From 1f489572e0ea5fbe2cfcb552b187927fa37813a2 Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Wed, 29 Mar 2023 18:35:21 +0300
Subject: [PATCH 01/24] basic sql store
---
packages/mongodb-store/README.md | 2 +-
packages/mysql-store/.npmignore | 6 ++
packages/mysql-store/README.md | 30 +++++++
packages/mysql-store/jest.config.json | 3 +
packages/mysql-store/package.json | 43 ++++++++++
packages/mysql-store/src/index.ts | 1 +
packages/mysql-store/src/store.ts | 10 +++
packages/mysql-store/tsconfig.json | 10 +++
packages/mysql-store/tsup.config.json | 6 ++
packages/sql-store/.npmignore | 6 ++
packages/sql-store/README.md | 32 ++++++++
packages/sql-store/jest.config.json | 3 +
packages/sql-store/package.json | 43 ++++++++++
packages/sql-store/src/index.ts | 1 +
packages/sql-store/src/mappers/collection.ts | 8 ++
packages/sql-store/src/mappers/field.ts | 42 ++++++++++
packages/sql-store/src/mappers/index.ts | 3 +
.../sql-store/src/mappers/store-index.test.ts | 27 +++++++
packages/sql-store/src/mappers/store-index.ts | 76 +++++++++++++++++
packages/sql-store/src/queries/connection.ts | 3 +
packages/sql-store/src/queries/index.ts | 4 +
.../src/queries/list-table-columns.ts | 24 ++++++
.../src/queries/list-table-statistics.ts | 25 ++++++
packages/sql-store/src/queries/list-tables.ts | 15 ++++
packages/sql-store/src/store.ts | 81 +++++++++++++++++++
packages/sql-store/tsconfig.json | 10 +++
packages/sql-store/tsup.config.json | 6 ++
yarn.lock | 50 +++++++++++-
28 files changed, 568 insertions(+), 2 deletions(-)
create mode 100644 packages/mysql-store/.npmignore
create mode 100644 packages/mysql-store/README.md
create mode 100644 packages/mysql-store/jest.config.json
create mode 100644 packages/mysql-store/package.json
create mode 100644 packages/mysql-store/src/index.ts
create mode 100644 packages/mysql-store/src/store.ts
create mode 100644 packages/mysql-store/tsconfig.json
create mode 100644 packages/mysql-store/tsup.config.json
create mode 100644 packages/sql-store/.npmignore
create mode 100644 packages/sql-store/README.md
create mode 100644 packages/sql-store/jest.config.json
create mode 100644 packages/sql-store/package.json
create mode 100644 packages/sql-store/src/index.ts
create mode 100644 packages/sql-store/src/mappers/collection.ts
create mode 100644 packages/sql-store/src/mappers/field.ts
create mode 100644 packages/sql-store/src/mappers/index.ts
create mode 100644 packages/sql-store/src/mappers/store-index.test.ts
create mode 100644 packages/sql-store/src/mappers/store-index.ts
create mode 100644 packages/sql-store/src/queries/connection.ts
create mode 100644 packages/sql-store/src/queries/index.ts
create mode 100644 packages/sql-store/src/queries/list-table-columns.ts
create mode 100644 packages/sql-store/src/queries/list-table-statistics.ts
create mode 100644 packages/sql-store/src/queries/list-tables.ts
create mode 100644 packages/sql-store/src/store.ts
create mode 100644 packages/sql-store/tsconfig.json
create mode 100644 packages/sql-store/tsup.config.json
diff --git a/packages/mongodb-store/README.md b/packages/mongodb-store/README.md
index 1dc51c2..5e55ee3 100644
--- a/packages/mongodb-store/README.md
+++ b/packages/mongodb-store/README.md
@@ -14,7 +14,7 @@ npm install @neuledge/mongodb-store
import { Engine } from '@neuledge/engine';
import { MongoDBStore } from '@neuledge/mongodb-store';
-const store = store: new MongoDBStore({
+const store = new MongoDBStore({
url: process.env.MONGODB_URL ?? 'mongodb://localhost:27017',
name: process.env.MONGODB_DATABASE ?? 'my-database',
});
diff --git a/packages/mysql-store/.npmignore b/packages/mysql-store/.npmignore
new file mode 100644
index 0000000..d2ee3b5
--- /dev/null
+++ b/packages/mysql-store/.npmignore
@@ -0,0 +1,6 @@
+/*
+!/dist/*.js
+!/dist/*.js.map
+!/dist/*.mjs
+!/dist/*.mjs.map
+!/dist/**/*.d.ts
\ No newline at end of file
diff --git a/packages/mysql-store/README.md b/packages/mysql-store/README.md
new file mode 100644
index 0000000..ad5cf63
--- /dev/null
+++ b/packages/mysql-store/README.md
@@ -0,0 +1,30 @@
+# Neuledge MySQL Store
+
+A store for [Neuledge Engine](https://neuledge.com) that uses [MySQL](https://www.mysql.com) database as a backend.
+
+## 📦 Installation
+
+```bash
+npm install @neuledge/mysql-store
+```
+
+## 🚀 Getting started
+
+```ts
+import { MySQLStore } from '@neuledge/mysql-store';
+
+const store = new MySQLStore({
+ uri: process.env.MYSQL_URI ?? 'mysql://localhost:3306',
+ database: process.env.MYSQL_DATABASE ?? 'my-database',
+});
+
+const engine = new Engine({
+ store,
+});
+```
+
+For more information, please refer to the [main repository](https://github.com/neuledge/engine-js).
+
+## 📄 License
+
+Neuledge is [Apache 2.0 licensed](https://github.com/neuledge/engine-js/blob/main/LICENSE).
diff --git a/packages/mysql-store/jest.config.json b/packages/mysql-store/jest.config.json
new file mode 100644
index 0000000..5901941
--- /dev/null
+++ b/packages/mysql-store/jest.config.json
@@ -0,0 +1,3 @@
+{
+ "preset": "@neuledge/jest-ts-preset"
+}
diff --git a/packages/mysql-store/package.json b/packages/mysql-store/package.json
new file mode 100644
index 0000000..be5cc6a
--- /dev/null
+++ b/packages/mysql-store/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@neuledge/mysql-store",
+ "version": "0.0.0",
+ "deascription": "MySQL store implementation for Neuledge Engine",
+ "keywords": [
+ "neuledge",
+ "mysql",
+ "store",
+ "database"
+ ],
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.js",
+ "exports": {
+ ".": {
+ "require": "./dist/index.js",
+ "import": "./dist/index.mjs",
+ "types": "./dist/index.d.ts"
+ }
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/neuledge/engine-js.git"
+ },
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "scripts": {
+ "types": "rimraf --glob dist/*.{d.ts,d.ts.map} dist/**/*.{d.ts,d.ts.map} && tsc --emitDeclarationOnly && tsc-alias",
+ "build": "rimraf --glob dist/*.{js,js.map,mjs,mjs.map} && tsup",
+ "test": "jest",
+ "lint": "eslint . --ext \"js,jsx,ts,tsx,mjs,cjs\"",
+ "lint:strict": "yarn lint --max-warnings 0"
+ },
+ "dependencies": {
+ "@neuledge/sql-store": "^0.0.0",
+ "mysql2": "^3.2.0"
+ }
+}
diff --git a/packages/mysql-store/src/index.ts b/packages/mysql-store/src/index.ts
new file mode 100644
index 0000000..d406816
--- /dev/null
+++ b/packages/mysql-store/src/index.ts
@@ -0,0 +1 @@
+export * from './store';
diff --git a/packages/mysql-store/src/store.ts b/packages/mysql-store/src/store.ts
new file mode 100644
index 0000000..7fee6fd
--- /dev/null
+++ b/packages/mysql-store/src/store.ts
@@ -0,0 +1,10 @@
+import { ConnectionOptions, PoolOptions, createPool } from 'mysql2/promise';
+import { SQLStore } from '@neuledge/sql-store';
+
+export type MySQLStoreOptions = PoolOptions & ConnectionOptions;
+
+export class MySQLStore extends SQLStore {
+ constructor(options: MySQLStoreOptions) {
+ super(createPool(options));
+ }
+}
diff --git a/packages/mysql-store/tsconfig.json b/packages/mysql-store/tsconfig.json
new file mode 100644
index 0000000..c67724d
--- /dev/null
+++ b/packages/mysql-store/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "@neuledge/tsconfig/base.json",
+ "compilerOptions": {
+ "baseUrl": "src",
+ "rootDir": "src",
+ "outDir": "dist"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "**/__ignore__/**"]
+}
diff --git a/packages/mysql-store/tsup.config.json b/packages/mysql-store/tsup.config.json
new file mode 100644
index 0000000..2f3a43d
--- /dev/null
+++ b/packages/mysql-store/tsup.config.json
@@ -0,0 +1,6 @@
+{
+ "entry": ["src/index.ts"],
+ "format": ["esm", "cjs"],
+ "sourcemap": true,
+ "shims": true
+}
diff --git a/packages/sql-store/.npmignore b/packages/sql-store/.npmignore
new file mode 100644
index 0000000..d2ee3b5
--- /dev/null
+++ b/packages/sql-store/.npmignore
@@ -0,0 +1,6 @@
+/*
+!/dist/*.js
+!/dist/*.js.map
+!/dist/*.mjs
+!/dist/*.mjs.map
+!/dist/**/*.d.ts
\ No newline at end of file
diff --git a/packages/sql-store/README.md b/packages/sql-store/README.md
new file mode 100644
index 0000000..79302fe
--- /dev/null
+++ b/packages/sql-store/README.md
@@ -0,0 +1,32 @@
+# Neuledge MySQL Store
+
+An store for [Neuledge Engine](https://neuledge.com) that uses [SQL](https://en.wikipedia.org/wiki/SQL) connection as a backend.
+
+This library is not intended to be used directly. It is a dependency of the SQL-based stores such as **MySQL** and **PostgreSQL**. For more information, please refer to the [main repository](https://github.com/neuledge/engine-js)
+
+## 📦 Installation
+
+```bash
+npm install @neuledge/sql-store
+```
+
+## 🚀 Getting started
+
+```ts
+import { SQLStore } from '@neuledge/mysql-store';
+
+// create a connection to your SQL database somehow
+const connection = createPool({
+ // ...
+});
+
+const store = new SQLStore(connection);
+
+const engine = new Engine({
+ store,
+});
+```
+
+## 📄 License
+
+Neuledge is [Apache 2.0 licensed](https://github.com/neuledge/engine-js/blob/main/LICENSE).
diff --git a/packages/sql-store/jest.config.json b/packages/sql-store/jest.config.json
new file mode 100644
index 0000000..5901941
--- /dev/null
+++ b/packages/sql-store/jest.config.json
@@ -0,0 +1,3 @@
+{
+ "preset": "@neuledge/jest-ts-preset"
+}
diff --git a/packages/sql-store/package.json b/packages/sql-store/package.json
new file mode 100644
index 0000000..00f552f
--- /dev/null
+++ b/packages/sql-store/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@neuledge/sql-store",
+ "version": "0.0.0",
+ "deascription": "Abstract SQL store implementation for Neuledge Engine",
+ "keywords": [
+ "neuledge",
+ "abstract",
+ "sql",
+ "store",
+ "database"
+ ],
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.js",
+ "exports": {
+ ".": {
+ "require": "./dist/index.js",
+ "import": "./dist/index.mjs",
+ "types": "./dist/index.d.ts"
+ }
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/neuledge/engine-js.git"
+ },
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "scripts": {
+ "types": "rimraf --glob dist/*.{d.ts,d.ts.map} dist/**/*.{d.ts,d.ts.map} && tsc --emitDeclarationOnly && tsc-alias",
+ "build": "rimraf --glob dist/*.{js,js.map,mjs,mjs.map} && tsup",
+ "test": "jest",
+ "lint": "eslint . --ext \"js,jsx,ts,tsx,mjs,cjs\"",
+ "lint:strict": "yarn lint --max-warnings 0"
+ },
+ "dependencies": {
+ "@neuledge/store": "^0.2.0"
+ }
+}
diff --git a/packages/sql-store/src/index.ts b/packages/sql-store/src/index.ts
new file mode 100644
index 0000000..d406816
--- /dev/null
+++ b/packages/sql-store/src/index.ts
@@ -0,0 +1 @@
+export * from './store';
diff --git a/packages/sql-store/src/mappers/collection.ts b/packages/sql-store/src/mappers/collection.ts
new file mode 100644
index 0000000..dac9b35
--- /dev/null
+++ b/packages/sql-store/src/mappers/collection.ts
@@ -0,0 +1,8 @@
+import { StoreCollection_Slim } from '@neuledge/store';
+import { SQLTable } from '@/queries';
+
+export const toStoreCollection_Slim = (
+ table: SQLTable,
+): StoreCollection_Slim => ({
+ name: table.table_name,
+});
diff --git a/packages/sql-store/src/mappers/field.ts b/packages/sql-store/src/mappers/field.ts
new file mode 100644
index 0000000..31992f6
--- /dev/null
+++ b/packages/sql-store/src/mappers/field.ts
@@ -0,0 +1,42 @@
+import { StoreError, StoreField, StoreShapeType } from '@neuledge/store';
+import { SQLColumn } from '@/queries';
+
+/**
+ * Map the SQL data types to the corresponding StoreShapeType
+ */
+const dataTypeMap: Record = {
+ varchar: 'string',
+ char: 'string',
+ text: 'string',
+ numeric: 'number',
+ decimal: 'number',
+ float: 'number',
+ double: 'number',
+ integer: 'number',
+ bigint: 'number',
+ boolean: 'boolean',
+ bytea: 'binary',
+ timestamp: 'date-time',
+ timestamptz: 'date-time',
+ json: 'json',
+ jsonb: 'json',
+};
+
+export const toStoreField = (column: SQLColumn): StoreField => {
+ const type = dataTypeMap[column.data_type];
+ if (!type) {
+ throw new StoreError(
+ StoreError.Code.NOT_SUPPORTED,
+ `Unsupported data type "${column.data_type}" for column "${column.column_name}"`,
+ );
+ }
+
+ return {
+ name: column.column_name,
+ type,
+ nullable: column.is_nullable === 'YES',
+ size: column.character_maximum_length,
+ precision: column.numeric_precision,
+ scale: column.numeric_scale,
+ };
+};
diff --git a/packages/sql-store/src/mappers/index.ts b/packages/sql-store/src/mappers/index.ts
new file mode 100644
index 0000000..93e2374
--- /dev/null
+++ b/packages/sql-store/src/mappers/index.ts
@@ -0,0 +1,3 @@
+export * from './collection';
+export * from './field';
+export * from './store-index';
diff --git a/packages/sql-store/src/mappers/store-index.test.ts b/packages/sql-store/src/mappers/store-index.test.ts
new file mode 100644
index 0000000..1904adb
--- /dev/null
+++ b/packages/sql-store/src/mappers/store-index.test.ts
@@ -0,0 +1,27 @@
+import { toStoreIndexes } from './store-index';
+
+describe('mappers/store-index', () => {
+ describe('toStoreIndexes()', () => {
+ it('should convert a single primary index', () => {
+ expect(
+ toStoreIndexes([
+ {
+ index_name: 'PRIMARY',
+ non_unique: 0,
+ column_name: 'field_name',
+ seq_in_index: 1,
+ collation: 'A',
+ },
+ ]),
+ ).toEqual([
+ {
+ name: 'PRIMARY',
+ unique: 'primary',
+ fields: {
+ field_name: { sort: 'asc' },
+ },
+ },
+ ]);
+ });
+ });
+});
diff --git a/packages/sql-store/src/mappers/store-index.ts b/packages/sql-store/src/mappers/store-index.ts
new file mode 100644
index 0000000..7cad662
--- /dev/null
+++ b/packages/sql-store/src/mappers/store-index.ts
@@ -0,0 +1,76 @@
+import { SQLStatistic } from '@/queries';
+import { StoreError, StoreIndex, StorePrimaryKey } from '@neuledge/store';
+
+export const toStoreIndexes = (
+ statistics: SQLStatistic[],
+): [primary: StorePrimaryKey, ...indexes: StoreIndex[]] => {
+ const indexStatistics = groupTableStatistics(statistics);
+
+ let primaryKey: StorePrimaryKey | undefined;
+ const indexes: StoreIndex[] = [];
+
+ for (const indexColumn of indexStatistics) {
+ const index = toStoreIndex(indexColumn);
+
+ if (index.unique === 'primary') {
+ primaryKey = index as StorePrimaryKey;
+ } else {
+ indexes.push(index);
+ }
+ }
+
+ if (!primaryKey) {
+ throw new StoreError(
+ StoreError.Code.INVALID_DATA,
+ `Primary key not found for collection "${name}"`,
+ );
+ }
+
+ return [primaryKey, ...indexes];
+};
+
+const toStoreIndex = (
+ indexStatistics: SQLStatistic[],
+): StoreIndex | StorePrimaryKey => {
+ const { index_name, non_unique, column_extra } = indexStatistics[0];
+
+ const index: StoreIndex | StorePrimaryKey = {
+ name: index_name,
+ unique: !non_unique,
+ fields: {},
+ };
+
+ if (index.unique && index_name === 'PRIMARY') {
+ index.unique = 'primary';
+
+ if (column_extra === 'auto_increment') {
+ (index as StorePrimaryKey).auto = 'increment';
+ }
+ }
+
+ indexStatistics.sort((a, b) => a.seq_in_index - b.seq_in_index);
+
+ for (const statistic of indexStatistics) {
+ index.fields[statistic.column_name] = {
+ sort: statistic.collation === 'A' ? 'asc' : 'desc',
+ };
+ }
+
+ return index;
+};
+
+const groupTableStatistics = (statistics: SQLStatistic[]): SQLStatistic[][] => {
+ const groupMap: Record = {};
+
+ for (const statistic of statistics) {
+ let group = groupMap[statistic.index_name];
+ if (!group) {
+ group = [];
+ groupMap[statistic.index_name] = group;
+ }
+
+ group.push(statistic);
+ }
+
+ return Object.values(groupMap);
+};
diff --git a/packages/sql-store/src/queries/connection.ts b/packages/sql-store/src/queries/connection.ts
new file mode 100644
index 0000000..0598c31
--- /dev/null
+++ b/packages/sql-store/src/queries/connection.ts
@@ -0,0 +1,3 @@
+export interface SQLConnection {
+ query(query: string, params?: unknown[]): Promise;
+}
diff --git a/packages/sql-store/src/queries/index.ts b/packages/sql-store/src/queries/index.ts
new file mode 100644
index 0000000..45599ce
--- /dev/null
+++ b/packages/sql-store/src/queries/index.ts
@@ -0,0 +1,4 @@
+export * from './connection';
+export * from './list-tables';
+export * from './list-table-columns';
+export * from './list-table-statistics';
diff --git a/packages/sql-store/src/queries/list-table-columns.ts b/packages/sql-store/src/queries/list-table-columns.ts
new file mode 100644
index 0000000..a52d591
--- /dev/null
+++ b/packages/sql-store/src/queries/list-table-columns.ts
@@ -0,0 +1,24 @@
+import { SQLConnection } from './connection';
+
+/**
+ * A table column from the information_schema.columns table.
+ */
+export interface SQLColumn {
+ column_name: string;
+ data_type: string;
+ character_maximum_length?: number | null;
+ numeric_precision?: number | null;
+ numeric_scale?: number | null;
+ is_nullable: 'YES' | 'NO';
+}
+
+export const listTableColumns = async (
+ connection: SQLConnection,
+ tableName: string,
+): Promise =>
+ connection.query(
+ `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, is_nullable
+FROM information_schema.columns
+WHERE table_name = ? AND table_schema = DATABASE()`,
+ [tableName],
+ );
diff --git a/packages/sql-store/src/queries/list-table-statistics.ts b/packages/sql-store/src/queries/list-table-statistics.ts
new file mode 100644
index 0000000..f47560c
--- /dev/null
+++ b/packages/sql-store/src/queries/list-table-statistics.ts
@@ -0,0 +1,25 @@
+import { SQLConnection } from './connection';
+
+/**
+ * A table statistic row from the information_schema.statistics table.
+ */
+export interface SQLStatistic {
+ index_name: string;
+ column_name: string;
+ seq_in_index: number;
+ collation: 'A' | 'D';
+ non_unique: number;
+ column_extra?: string;
+}
+
+export const listTableStatistics = async (
+ connection: SQLConnection,
+ tableName: string,
+): Promise =>
+ connection.query(
+ `SELECT index_name, column_name, seq_in_index, collation, non_unique, extra AS column_extra
+ FROM information_schema.statistics INNER JOIN information_schema.columns USING (table_schema, table_name, column_name)
+ WHERE table_schema = DATABASE() AND table_name = ?
+ ORDER BY index_name, seq_in_index`,
+ [tableName],
+ );
diff --git a/packages/sql-store/src/queries/list-tables.ts b/packages/sql-store/src/queries/list-tables.ts
new file mode 100644
index 0000000..e29a81e
--- /dev/null
+++ b/packages/sql-store/src/queries/list-tables.ts
@@ -0,0 +1,15 @@
+import { SQLConnection } from './connection';
+
+/**
+ * The tables in the database. This is a view of the `information_schema.tables` table.
+ */
+export interface SQLTable {
+ table_name: string;
+}
+
+export const listTables = async (
+ connection: SQLConnection,
+): Promise =>
+ connection.query(
+ `SELECT table_name AS name FROM information_schema.tables WHERE table_schema = DATABASE()`,
+ );
diff --git a/packages/sql-store/src/store.ts b/packages/sql-store/src/store.ts
new file mode 100644
index 0000000..48b168d
--- /dev/null
+++ b/packages/sql-store/src/store.ts
@@ -0,0 +1,81 @@
+import {
+ Store,
+ StoreCollection,
+ StoreCollection_Slim,
+ StoreDeleteOptions,
+ StoreDescribeCollectionOptions,
+ StoreDocument,
+ StoreDropCollectionOptions,
+ StoreEnsureCollectionOptions,
+ StoreFindOptions,
+ StoreInsertOptions,
+ StoreInsertionResponse,
+ StoreList,
+ StoreMutationResponse,
+ StoreUpdateOptions,
+} from '@neuledge/store';
+import {
+ SQLConnection,
+ listTableColumns,
+ listTableStatistics,
+ listTables,
+} from './queries';
+import {
+ toStoreCollection_Slim,
+ toStoreField,
+ toStoreIndexes,
+} from './mappers';
+
+export class SQLStore implements Store {
+ constructor(public readonly connection: SQLConnection) {}
+
+ async listCollections(): Promise {
+ const tables = await listTables(this.connection);
+ return tables.map((table) => toStoreCollection_Slim(table));
+ }
+
+ async describeCollection(
+ options: StoreDescribeCollectionOptions,
+ ): Promise {
+ const { name } = options.collection;
+
+ const [columns, statistics] = await Promise.all([
+ listTableColumns(this.connection, name),
+ listTableStatistics(this.connection, name),
+ ]);
+
+ const fields = columns.map((column) => toStoreField(column));
+ const indexes = toStoreIndexes(statistics);
+
+ return {
+ name,
+ primaryKey: indexes[0],
+ indexes: Object.fromEntries(indexes.map((index) => [index.name, index])),
+ fields: Object.fromEntries(fields.map((field) => [field.name, field])),
+ };
+ }
+
+ ensureCollection(options: StoreEnsureCollectionOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ dropCollection(options: StoreDropCollectionOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ find(options: StoreFindOptions): Promise> {
+ throw new Error('Method not implemented.');
+ }
+
+ insert(options: StoreInsertOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ update(options: StoreUpdateOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ delete(options: StoreDeleteOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
+}
diff --git a/packages/sql-store/tsconfig.json b/packages/sql-store/tsconfig.json
new file mode 100644
index 0000000..c67724d
--- /dev/null
+++ b/packages/sql-store/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "@neuledge/tsconfig/base.json",
+ "compilerOptions": {
+ "baseUrl": "src",
+ "rootDir": "src",
+ "outDir": "dist"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "**/__ignore__/**"]
+}
diff --git a/packages/sql-store/tsup.config.json b/packages/sql-store/tsup.config.json
new file mode 100644
index 0000000..2f3a43d
--- /dev/null
+++ b/packages/sql-store/tsup.config.json
@@ -0,0 +1,6 @@
+{
+ "entry": ["src/index.ts"],
+ "format": ["esm", "cjs"],
+ "sourcemap": true,
+ "shims": true
+}
diff --git a/yarn.lock b/yarn.lock
index d4c0766..32b5f0e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3196,6 +3196,11 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+denque@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
+ integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
+
depd@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@@ -4376,6 +4381,13 @@ iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
+iconv-lite@^0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@@ -5423,6 +5435,11 @@ log-update@^4.0.0:
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
+long@^5.2.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f"
+ integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==
+
loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -5728,6 +5745,20 @@ mylas@^2.1.9:
resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.13.tgz#1e23b37d58fdcc76e15d8a5ed23f9ae9fc0cbdf4"
integrity sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==
+mysql2@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.2.0.tgz#3613a8903bcb7ade0ae35b29945a0378eb67da89"
+ integrity sha512-0Vn6a9WSrq6fWwvPgrvIwnOCldiEcgbzapVRDAtDZ4cMTxN7pnGqCTx8EG32S/NYXl6AXkdO+9hV1tSIi/LigA==
+ dependencies:
+ denque "^2.1.0"
+ generate-function "^2.3.1"
+ iconv-lite "^0.6.3"
+ long "^5.2.1"
+ lru-cache "^7.14.1"
+ named-placeholders "^1.1.3"
+ seq-queue "^0.0.5"
+ sqlstring "^2.3.2"
+
mz@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
@@ -5737,6 +5768,13 @@ mz@^2.7.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
+named-placeholders@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351"
+ integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==
+ dependencies:
+ lru-cache "^7.14.1"
+
napi-build-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
@@ -6629,7 +6667,7 @@ safe-stable-stringify@^2.0.0, safe-stable-stringify@^2.3.0, safe-stable-stringif
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==
-"safer-buffer@>= 2.1.2 < 3":
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -6682,6 +6720,11 @@ sentence-case@^3.0.4:
tslib "^2.0.3"
upper-case-first "^2.0.2"
+seq-queue@^0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e"
+ integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==
+
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -6917,6 +6960,11 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+sqlstring@^2.3.2:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c"
+ integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==
+
stack-utils@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
From 9cbc26c06b2d9b37d7b522bada142748e187a26c Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Wed, 29 Mar 2023 23:46:54 +0300
Subject: [PATCH 02/24] update main readme stores
---
packages/engine/README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/engine/README.md b/packages/engine/README.md
index 80fb8e3..497079d 100644
--- a/packages/engine/README.md
+++ b/packages/engine/README.md
@@ -12,8 +12,9 @@
MongoDB ⇄
- MySQL (soon) ⇄
- PostgreSQL (soon)
+ MySQL ⇄
+ PostgreSQL ⇄
+ [Request more](https://github.com/neuledge/engine-js/issues/new)
From 4dd29d9114d0abf8132fa652258f799bf5fa38b3 Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Wed, 29 Mar 2023 23:48:46 +0300
Subject: [PATCH 03/24] update readme
---
packages/engine/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/engine/README.md b/packages/engine/README.md
index 497079d..fabd823 100644
--- a/packages/engine/README.md
+++ b/packages/engine/README.md
@@ -14,7 +14,7 @@
MongoDB ⇄
MySQL ⇄
PostgreSQL ⇄
- [Request more](https://github.com/neuledge/engine-js/issues/new)
+ Your DB (request)
From b5aee0665e79b2bb9a1ef83b5f41c9a0339a5cf7 Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Thu, 30 Mar 2023 00:05:40 +0300
Subject: [PATCH 04/24] use mysql
---
packages/mysql-store/jest.config.json | 3 -
packages/mysql-store/package.json | 4 +-
packages/mysql-store/src/store.ts | 25 +++-
packages/sql-store/src/queries/connection.ts | 2 +-
yarn.lock | 115 +++++++++++--------
5 files changed, 92 insertions(+), 57 deletions(-)
delete mode 100644 packages/mysql-store/jest.config.json
diff --git a/packages/mysql-store/jest.config.json b/packages/mysql-store/jest.config.json
deleted file mode 100644
index 5901941..0000000
--- a/packages/mysql-store/jest.config.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "preset": "@neuledge/jest-ts-preset"
-}
diff --git a/packages/mysql-store/package.json b/packages/mysql-store/package.json
index be5cc6a..7571c1b 100644
--- a/packages/mysql-store/package.json
+++ b/packages/mysql-store/package.json
@@ -32,12 +32,12 @@
"scripts": {
"types": "rimraf --glob dist/*.{d.ts,d.ts.map} dist/**/*.{d.ts,d.ts.map} && tsc --emitDeclarationOnly && tsc-alias",
"build": "rimraf --glob dist/*.{js,js.map,mjs,mjs.map} && tsup",
- "test": "jest",
"lint": "eslint . --ext \"js,jsx,ts,tsx,mjs,cjs\"",
"lint:strict": "yarn lint --max-warnings 0"
},
"dependencies": {
"@neuledge/sql-store": "^0.0.0",
- "mysql2": "^3.2.0"
+ "@types/mysql": "^2.15.21",
+ "mysql": "^2.18.1"
}
}
diff --git a/packages/mysql-store/src/store.ts b/packages/mysql-store/src/store.ts
index 7fee6fd..11829c8 100644
--- a/packages/mysql-store/src/store.ts
+++ b/packages/mysql-store/src/store.ts
@@ -1,10 +1,29 @@
-import { ConnectionOptions, PoolOptions, createPool } from 'mysql2/promise';
+import { Pool, PoolConfig, createPool } from 'mysql';
import { SQLStore } from '@neuledge/sql-store';
-export type MySQLStoreOptions = PoolOptions & ConnectionOptions;
+export type MySQLStoreOptions = PoolConfig;
export class MySQLStore extends SQLStore {
+ private pool: Pool;
+
constructor(options: MySQLStoreOptions) {
- super(createPool(options));
+ const pool = createPool(options);
+
+ super({
+ query: (sql, values) =>
+ new Promise((resolve, reject) =>
+ pool.query(sql, values, (error, results) =>
+ error ? reject(error) : resolve(results),
+ ),
+ ),
+ });
+
+ this.pool = pool;
+ }
+
+ async close(): Promise {
+ await new Promise((resolve, reject) =>
+ this.pool.end((error) => (error ? reject(error) : resolve())),
+ );
}
}
diff --git a/packages/sql-store/src/queries/connection.ts b/packages/sql-store/src/queries/connection.ts
index 0598c31..5176269 100644
--- a/packages/sql-store/src/queries/connection.ts
+++ b/packages/sql-store/src/queries/connection.ts
@@ -1,3 +1,3 @@
export interface SQLConnection {
- query(query: string, params?: unknown[]): Promise;
+ query(sql: string, params?: unknown[]): Promise;
}
diff --git a/yarn.lock b/yarn.lock
index 32b5f0e..bcee074 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1980,6 +1980,13 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
+"@types/mysql@^2.15.21":
+ version "2.15.21"
+ resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.21.tgz#7516cba7f9d077f980100c85fd500c8210bd5e45"
+ integrity sha512-NPotx5CVful7yB+qZbWtXL2fA4e7aEHkihHLjklc6ID8aq7bhguHgeIoC1EmSNTAuCgI6ZXrjt2ZSaXnYX0EUg==
+ dependencies:
+ "@types/node" "*"
+
"@types/node@*", "@types/node@^18.14.2":
version "18.15.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.5.tgz#3af577099a99c61479149b716183e70b5239324a"
@@ -2556,6 +2563,11 @@ better-path-resolve@1.0.0:
dependencies:
is-windows "^1.0.0"
+bignumber.js@9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075"
+ integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==
+
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
@@ -3020,6 +3032,11 @@ cookie@^0.5.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
+core-util-is@~1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+ integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
cosmiconfig@8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.0.0.tgz#e9feae014eab580f858f8a0288f38997a7bebe97"
@@ -3196,11 +3213,6 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
-denque@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
- integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
-
depd@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@@ -4381,13 +4393,6 @@ iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
-iconv-lite@^0.6.3:
- version "0.6.3"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
- integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
- dependencies:
- safer-buffer ">= 2.1.2 < 3.0.0"
-
ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@@ -4442,7 +4447,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4:
+inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -4732,6 +4737,11 @@ is-windows@^1.0.0, is-windows@^1.0.1:
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -5435,11 +5445,6 @@ log-update@^4.0.0:
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
-long@^5.2.1:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f"
- integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==
-
loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -5745,19 +5750,15 @@ mylas@^2.1.9:
resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.13.tgz#1e23b37d58fdcc76e15d8a5ed23f9ae9fc0cbdf4"
integrity sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==
-mysql2@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.2.0.tgz#3613a8903bcb7ade0ae35b29945a0378eb67da89"
- integrity sha512-0Vn6a9WSrq6fWwvPgrvIwnOCldiEcgbzapVRDAtDZ4cMTxN7pnGqCTx8EG32S/NYXl6AXkdO+9hV1tSIi/LigA==
+mysql@^2.18.1:
+ version "2.18.1"
+ resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717"
+ integrity sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==
dependencies:
- denque "^2.1.0"
- generate-function "^2.3.1"
- iconv-lite "^0.6.3"
- long "^5.2.1"
- lru-cache "^7.14.1"
- named-placeholders "^1.1.3"
- seq-queue "^0.0.5"
- sqlstring "^2.3.2"
+ bignumber.js "9.0.0"
+ readable-stream "2.3.7"
+ safe-buffer "5.1.2"
+ sqlstring "2.3.1"
mz@^2.7.0:
version "2.7.0"
@@ -5768,13 +5769,6 @@ mz@^2.7.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
-named-placeholders@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351"
- integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==
- dependencies:
- lru-cache "^7.14.1"
-
napi-build-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
@@ -6280,6 +6274,11 @@ pretty-format@^29.0.0, pretty-format@^29.5.0:
ansi-styles "^5.0.0"
react-is "^18.0.0"
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
process-warning@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.1.0.tgz#1e60e3bfe8183033bbc1e702c2da74f099422d1a"
@@ -6436,6 +6435,19 @@ read@^1.0.7:
dependencies:
mute-stream "~0.0.4"
+readable-stream@2.3.7:
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+ integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
readable-stream@^3.1.1, readable-stream@^3.4.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
@@ -6634,6 +6646,11 @@ rxjs@^7.5.5:
dependencies:
tslib "^2.1.0"
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@@ -6667,7 +6684,7 @@ safe-stable-stringify@^2.0.0, safe-stable-stringify@^2.3.0, safe-stable-stringif
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==
-"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
+"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -6720,11 +6737,6 @@ sentence-case@^3.0.4:
tslib "^2.0.3"
upper-case-first "^2.0.2"
-seq-queue@^0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e"
- integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==
-
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -6960,10 +6972,10 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
-sqlstring@^2.3.2:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c"
- integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==
+sqlstring@2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.1.tgz#475393ff9e91479aea62dcaf0ca3d14983a7fb40"
+ integrity sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==
stack-utils@^2.0.3:
version "2.0.6"
@@ -7050,6 +7062,13 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
@@ -7596,7 +7615,7 @@ urlpattern-polyfill@^6.0.2:
dependencies:
braces "^3.0.2"
-util-deprecate@^1.0.1:
+util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
From 919f2e9dc9f5e994e771060cb0fee0eba388a66c Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Sun, 2 Apr 2023 19:33:41 +0300
Subject: [PATCH 05/24] add postgresql
---
package.json | 2 +-
packages/mysql-store/package.json | 1 +
.../src/queries/connection.ts | 0
.../src/queries/index.ts | 0
.../src/queries/list-table-columns.ts | 44 +++++
.../src/queries/list-table-statistics.ts | 25 +++
.../src/queries/list-tables.ts | 8 +-
packages/mysql-store/src/store.ts | 82 +++++++-
packages/postgresql-store/.npmignore | 6 +
packages/postgresql-store/README.md | 30 +++
packages/postgresql-store/package.json | 45 +++++
packages/postgresql-store/src/index.ts | 1 +
.../src/queries/connection.ts | 3 +
.../postgresql-store/src/queries/index.ts | 4 +
.../src/queries/list-table-columns.ts | 77 ++++++++
.../src/queries/list-table-statistics.ts | 40 ++++
.../src/queries/list-tables.ts | 15 ++
.../src/store.ts | 66 ++++---
packages/postgresql-store/tsconfig.json | 10 +
packages/postgresql-store/tsup.config.json | 6 +
packages/sql-store/README.md | 16 +-
packages/sql-store/src/index.ts | 3 +-
packages/sql-store/src/logic/collections.ts | 115 +++++++++++
packages/sql-store/src/logic/index.ts | 1 +
packages/sql-store/src/mappers/collection.ts | 5 +-
packages/sql-store/src/mappers/field.ts | 36 ++--
.../sql-store/src/mappers/store-index.test.ts | 29 +--
packages/sql-store/src/mappers/store-index.ts | 81 +++-----
.../src/queries/list-table-columns.ts | 24 ---
.../src/queries/list-table-statistics.ts | 25 ---
yarn.lock | 179 ++++++++++++++----
31 files changed, 740 insertions(+), 239 deletions(-)
rename packages/{sql-store => mysql-store}/src/queries/connection.ts (100%)
rename packages/{sql-store => mysql-store}/src/queries/index.ts (100%)
create mode 100644 packages/mysql-store/src/queries/list-table-columns.ts
create mode 100644 packages/mysql-store/src/queries/list-table-statistics.ts
rename packages/{sql-store => mysql-store}/src/queries/list-tables.ts (56%)
create mode 100644 packages/postgresql-store/.npmignore
create mode 100644 packages/postgresql-store/README.md
create mode 100644 packages/postgresql-store/package.json
create mode 100644 packages/postgresql-store/src/index.ts
create mode 100644 packages/postgresql-store/src/queries/connection.ts
create mode 100644 packages/postgresql-store/src/queries/index.ts
create mode 100644 packages/postgresql-store/src/queries/list-table-columns.ts
create mode 100644 packages/postgresql-store/src/queries/list-table-statistics.ts
create mode 100644 packages/postgresql-store/src/queries/list-tables.ts
rename packages/{sql-store => postgresql-store}/src/store.ts (61%)
create mode 100644 packages/postgresql-store/tsconfig.json
create mode 100644 packages/postgresql-store/tsup.config.json
create mode 100644 packages/sql-store/src/logic/collections.ts
create mode 100644 packages/sql-store/src/logic/index.ts
delete mode 100644 packages/sql-store/src/queries/list-table-columns.ts
delete mode 100644 packages/sql-store/src/queries/list-table-statistics.ts
diff --git a/package.json b/package.json
index c3800ef..3da1245 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"ts-node": "^10.9.1",
"tsc-alias": "^1.8.3",
"tsup": "^6.6.3",
- "turbo": "^1.8.3",
+ "turbo": "^1.8.8",
"typescript": "^5.0.2"
}
}
diff --git a/packages/mysql-store/package.json b/packages/mysql-store/package.json
index 7571c1b..7a44483 100644
--- a/packages/mysql-store/package.json
+++ b/packages/mysql-store/package.json
@@ -36,6 +36,7 @@
"lint:strict": "yarn lint --max-warnings 0"
},
"dependencies": {
+ "@neuledge/store": "^0.2.0",
"@neuledge/sql-store": "^0.0.0",
"@types/mysql": "^2.15.21",
"mysql": "^2.18.1"
diff --git a/packages/sql-store/src/queries/connection.ts b/packages/mysql-store/src/queries/connection.ts
similarity index 100%
rename from packages/sql-store/src/queries/connection.ts
rename to packages/mysql-store/src/queries/connection.ts
diff --git a/packages/sql-store/src/queries/index.ts b/packages/mysql-store/src/queries/index.ts
similarity index 100%
rename from packages/sql-store/src/queries/index.ts
rename to packages/mysql-store/src/queries/index.ts
diff --git a/packages/mysql-store/src/queries/list-table-columns.ts b/packages/mysql-store/src/queries/list-table-columns.ts
new file mode 100644
index 0000000..6c3ade8
--- /dev/null
+++ b/packages/mysql-store/src/queries/list-table-columns.ts
@@ -0,0 +1,44 @@
+import { StoreShapeType } from '@neuledge/store';
+import { SQLConnection } from './connection';
+
+/**
+ * A table column from the information_schema.columns table.
+ */
+export interface MySQLColumn {
+ column_name: string;
+ data_type: string;
+ character_maximum_length: number | null;
+ numeric_precision: number | null;
+ numeric_scale: number | null;
+ is_nullable: 1 | 0;
+ is_auto_increment: 1 | 0;
+}
+
+export const listTableColumns = async (
+ tableName: string,
+ connection: SQLConnection,
+): Promise =>
+ connection.query(
+ `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, (is_nullable = 'YES') AS is_nullable, extra LIKE '%auto_increment%' AS is_auto_increment
+FROM information_schema.columns
+WHERE table_schema = DATABASE() AND table_name = ?`,
+ [tableName],
+ );
+
+export const dataTypeMap: Record = {
+ varchar: 'string',
+ char: 'string',
+ text: 'string',
+ numeric: 'number',
+ decimal: 'number',
+ float: 'number',
+ double: 'number',
+ integer: 'number',
+ bigint: 'number',
+ boolean: 'boolean',
+ bytea: 'binary',
+ timestamp: 'date-time',
+ timestamptz: 'date-time',
+ json: 'json',
+ jsonb: 'json',
+};
diff --git a/packages/mysql-store/src/queries/list-table-statistics.ts b/packages/mysql-store/src/queries/list-table-statistics.ts
new file mode 100644
index 0000000..5f3ad45
--- /dev/null
+++ b/packages/mysql-store/src/queries/list-table-statistics.ts
@@ -0,0 +1,25 @@
+import { SQLConnection } from './connection';
+
+/**
+ * A table statistic row from the information_schema.statistics table.
+ */
+export interface MySQLIndexAttribute {
+ index_name: string;
+ column_name: string;
+ seq_in_index: number;
+ direction: 'ASC' | 'DESC';
+ is_unique: 1 | 0;
+ is_primary: 1 | 0;
+}
+
+export const listIndexAttributes = async (
+ tableName: string,
+ connection: SQLConnection,
+): Promise =>
+ connection.query(
+ `SELECT index_name, column_name, seq_in_index, CASE collation WHEN 'A' THEN 'ASC' ELSE 'DESC' END AS direction, non_unique, (index_name == 'PRIMARY') as is_primary
+ FROM information_schema.statistics
+ WHERE table_schema = DATABASE() AND table_name = ?
+ ORDER BY index_name, seq_in_index`,
+ [tableName],
+ );
diff --git a/packages/sql-store/src/queries/list-tables.ts b/packages/mysql-store/src/queries/list-tables.ts
similarity index 56%
rename from packages/sql-store/src/queries/list-tables.ts
rename to packages/mysql-store/src/queries/list-tables.ts
index e29a81e..828fbbc 100644
--- a/packages/sql-store/src/queries/list-tables.ts
+++ b/packages/mysql-store/src/queries/list-tables.ts
@@ -3,13 +3,13 @@ import { SQLConnection } from './connection';
/**
* The tables in the database. This is a view of the `information_schema.tables` table.
*/
-export interface SQLTable {
+export interface MySQLTable {
table_name: string;
}
export const listTables = async (
connection: SQLConnection,
-): Promise =>
- connection.query(
- `SELECT table_name AS name FROM information_schema.tables WHERE table_schema = DATABASE()`,
+): Promise =>
+ connection.query(
+ `SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()`,
);
diff --git a/packages/mysql-store/src/store.ts b/packages/mysql-store/src/store.ts
index 11829c8..f51c672 100644
--- a/packages/mysql-store/src/store.ts
+++ b/packages/mysql-store/src/store.ts
@@ -1,29 +1,95 @@
import { Pool, PoolConfig, createPool } from 'mysql';
-import { SQLStore } from '@neuledge/sql-store';
+import {
+ Store,
+ StoreCollection,
+ StoreCollection_Slim,
+ StoreDeleteOptions,
+ StoreDescribeCollectionOptions,
+ StoreDocument,
+ StoreDropCollectionOptions,
+ StoreEnsureCollectionOptions,
+ StoreFindOptions,
+ StoreInsertOptions,
+ StoreInsertionResponse,
+ StoreList,
+ StoreMutationResponse,
+ StoreUpdateOptions,
+} from '@neuledge/store';
+import {
+ SQLConnection,
+ dataTypeMap,
+ listTableColumns,
+ listIndexAttributes,
+ listTables,
+} from './queries';
+import { getCollection, getStoreCollections } from '@neuledge/sql-store';
export type MySQLStoreOptions = PoolConfig;
-export class MySQLStore extends SQLStore {
+export class MySQLStore implements Store {
private pool: Pool;
+ private connection: SQLConnection;
constructor(options: MySQLStoreOptions) {
- const pool = createPool(options);
+ this.pool = createPool(options);
- super({
+ this.connection = {
query: (sql, values) =>
new Promise((resolve, reject) =>
- pool.query(sql, values, (error, results) =>
+ this.pool.query(sql, values, (error, results) =>
error ? reject(error) : resolve(results),
),
),
- });
-
- this.pool = pool;
+ };
}
+ // connection methods
+
async close(): Promise {
await new Promise((resolve, reject) =>
this.pool.end((error) => (error ? reject(error) : resolve())),
);
}
+
+ // store methods
+
+ async listCollections(): Promise {
+ return getStoreCollections(listTables, this.connection);
+ }
+
+ async describeCollection(
+ options: StoreDescribeCollectionOptions,
+ ): Promise {
+ return getCollection(
+ options,
+ listTableColumns,
+ listIndexAttributes,
+ dataTypeMap,
+ this.connection,
+ );
+ }
+
+ ensureCollection(options: StoreEnsureCollectionOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ dropCollection(options: StoreDropCollectionOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ find(options: StoreFindOptions): Promise> {
+ throw new Error('Method not implemented.');
+ }
+
+ insert(options: StoreInsertOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ update(options: StoreUpdateOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ delete(options: StoreDeleteOptions): Promise {
+ throw new Error('Method not implemented.');
+ }
}
diff --git a/packages/postgresql-store/.npmignore b/packages/postgresql-store/.npmignore
new file mode 100644
index 0000000..d2ee3b5
--- /dev/null
+++ b/packages/postgresql-store/.npmignore
@@ -0,0 +1,6 @@
+/*
+!/dist/*.js
+!/dist/*.js.map
+!/dist/*.mjs
+!/dist/*.mjs.map
+!/dist/**/*.d.ts
\ No newline at end of file
diff --git a/packages/postgresql-store/README.md b/packages/postgresql-store/README.md
new file mode 100644
index 0000000..f438fe2
--- /dev/null
+++ b/packages/postgresql-store/README.md
@@ -0,0 +1,30 @@
+# Neuledge PostgreSQL Store
+
+A store for [Neuledge Engine](https://neuledge.com) that uses [PostgreSQL](https://www.postgresql.org) database as a backend.
+
+## 📦 Installation
+
+```bash
+npm install @neuledge/postgresql-store
+```
+
+## 🚀 Getting started
+
+```ts
+import { PostgreSQLStore } from '@neuledge/postgresql-store';
+
+const store = new PostgreSQLStore({
+ uri: process.env.MYSQL_URI ?? 'mysql://localhost:3306',
+ database: process.env.MYSQL_DATABASE ?? 'my-database',
+});
+
+const engine = new Engine({
+ store,
+});
+```
+
+For more information, please refer to the [main repository](https://github.com/neuledge/engine-js).
+
+## 📄 License
+
+Neuledge is [Apache 2.0 licensed](https://github.com/neuledge/engine-js/blob/main/LICENSE).
diff --git a/packages/postgresql-store/package.json b/packages/postgresql-store/package.json
new file mode 100644
index 0000000..9d64233
--- /dev/null
+++ b/packages/postgresql-store/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@neuledge/postgresql-store",
+ "version": "0.0.0",
+ "deascription": "PostgreSQL store implementation for Neuledge Engine",
+ "keywords": [
+ "neuledge",
+ "postgres",
+ "postgresql",
+ "store",
+ "database"
+ ],
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.js",
+ "exports": {
+ ".": {
+ "require": "./dist/index.js",
+ "import": "./dist/index.mjs",
+ "types": "./dist/index.d.ts"
+ }
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/neuledge/engine-js.git"
+ },
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "scripts": {
+ "types": "rimraf --glob dist/*.{d.ts,d.ts.map} dist/**/*.{d.ts,d.ts.map} && tsc --emitDeclarationOnly && tsc-alias",
+ "build": "rimraf --glob dist/*.{js,js.map,mjs,mjs.map} && tsup",
+ "lint": "eslint . --ext \"js,jsx,ts,tsx,mjs,cjs\"",
+ "lint:strict": "yarn lint --max-warnings 0"
+ },
+ "dependencies": {
+ "@neuledge/store": "^0.2.0",
+ "@neuledge/sql-store": "^0.0.0",
+ "@types/pg": "^8.6.6",
+ "pg": "^8.10.0"
+ }
+}
diff --git a/packages/postgresql-store/src/index.ts b/packages/postgresql-store/src/index.ts
new file mode 100644
index 0000000..d406816
--- /dev/null
+++ b/packages/postgresql-store/src/index.ts
@@ -0,0 +1 @@
+export * from './store';
diff --git a/packages/postgresql-store/src/queries/connection.ts b/packages/postgresql-store/src/queries/connection.ts
new file mode 100644
index 0000000..5176269
--- /dev/null
+++ b/packages/postgresql-store/src/queries/connection.ts
@@ -0,0 +1,3 @@
+export interface SQLConnection {
+ query(sql: string, params?: unknown[]): Promise;
+}
diff --git a/packages/postgresql-store/src/queries/index.ts b/packages/postgresql-store/src/queries/index.ts
new file mode 100644
index 0000000..45599ce
--- /dev/null
+++ b/packages/postgresql-store/src/queries/index.ts
@@ -0,0 +1,4 @@
+export * from './connection';
+export * from './list-tables';
+export * from './list-table-columns';
+export * from './list-table-statistics';
diff --git a/packages/postgresql-store/src/queries/list-table-columns.ts b/packages/postgresql-store/src/queries/list-table-columns.ts
new file mode 100644
index 0000000..869e950
--- /dev/null
+++ b/packages/postgresql-store/src/queries/list-table-columns.ts
@@ -0,0 +1,77 @@
+import { StoreShapeType } from '@neuledge/store';
+import { SQLConnection } from './connection';
+
+/**
+ * A table column from the information_schema.columns table.
+ */
+export interface PostgreSQLColumn {
+ column_name: string;
+ data_type: string;
+ character_maximum_length: number | null;
+ numeric_precision: number | null;
+ numeric_scale: number | null;
+ is_nullable: boolean;
+ is_auto_increment: boolean;
+}
+
+export const listTableColumns = async (
+ tableName: string,
+ connection: SQLConnection,
+): Promise =>
+ connection.query(
+ `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, (is_nullable = 'YES') as is_nullable, column_default LIKE 'nextval(%)' AS is_auto_increment
+ FROM information_schema.columns
+ WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_name = ?`,
+ [tableName],
+ );
+
+export const dataTypeMap: Record = {
+ character: 'string',
+ 'character varying': 'string',
+ 'double precision': 'number',
+ smallint: 'number',
+ real: 'number',
+ 'timestamp without time zone': 'date-time',
+ 'timestamp with time zone': 'date-time',
+ 'time without time zone': 'date-time',
+ 'time with time zone': 'date-time',
+ 'interval year to month': 'date-time',
+ 'interval day to second': 'date-time',
+ 'bit varying': 'binary',
+ bit: 'binary',
+ varbit: 'binary',
+ bytea: 'binary',
+ text: 'string',
+ json: 'json',
+ jsonb: 'json',
+ uuid: 'string',
+ xml: 'string',
+ cidr: 'string',
+ inet: 'string',
+ macaddr: 'string',
+ tsvector: 'string',
+ tsquery: 'string',
+ regconfig: 'string',
+ regdictionary: 'string',
+ regnamespace: 'string',
+ regoper: 'string',
+ regoperator: 'string',
+ regproc: 'string',
+ regprocedure: 'string',
+ regrole: 'string',
+ regtype: 'string',
+ int2: 'number',
+ int4: 'number',
+ int8: 'number',
+ float4: 'number',
+ float8: 'number',
+ bool: 'boolean',
+ date: 'date-time',
+ time: 'date-time',
+ timestamp: 'date-time',
+ timestamptz: 'date-time',
+ interval: 'date-time',
+ numeric: 'number',
+ decimal: 'number',
+ money: 'number',
+};
diff --git a/packages/postgresql-store/src/queries/list-table-statistics.ts b/packages/postgresql-store/src/queries/list-table-statistics.ts
new file mode 100644
index 0000000..150bb55
--- /dev/null
+++ b/packages/postgresql-store/src/queries/list-table-statistics.ts
@@ -0,0 +1,40 @@
+import { SQLConnection } from './connection';
+
+/**
+ * A table statistic row from the information_schema.statistics table.
+ */
+export interface PostgreSQLIndexAttribute {
+ index_name: string;
+ column_name: string;
+ seq_in_index: number;
+ direction: 'ASC' | 'DESC';
+ nulls: 'FIRST' | 'LAST';
+ is_unique: boolean;
+ is_primary: boolean;
+}
+
+export const listIndexAttributes = async (
+ tableName: string,
+ connection: SQLConnection,
+): Promise =>
+ connection.query(
+ `SELECT
+ irel.relname AS index_name,
+ a.attname AS column_name,
+ c.ordinality as seq_in_index,
+ CASE o.option & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction,
+ CASE o.option & 2 WHEN 2 THEN 'FIRST' ELSE 'LAST' END AS nulls,
+ i.indisunique AS is_unique,
+ i.indisprimary AS is_primary
+ FROM pg_index AS i
+ JOIN pg_class AS trel ON trel.oid = i.indrelid
+ JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
+ JOIN pg_class AS irel ON irel.oid = i.indexrelid
+ CROSS JOIN LATERAL unnest (i.indkey) WITH ORDINALITY AS c (colnum, ordinality)
+ LEFT JOIN LATERAL unnest (i.indoption) WITH ORDINALITY AS o (option, ordinality)
+ ON c.ordinality = o.ordinality
+ JOIN pg_attribute AS a ON trel.oid = a.attrelid AND a.attnum = c.colnum
+ WHERE tnsp.nspname = current_schema() AND trel.relname = ?
+ ORDER BY index_name, seq_in_index`,
+ [tableName],
+ );
diff --git a/packages/postgresql-store/src/queries/list-tables.ts b/packages/postgresql-store/src/queries/list-tables.ts
new file mode 100644
index 0000000..bb4e5c5
--- /dev/null
+++ b/packages/postgresql-store/src/queries/list-tables.ts
@@ -0,0 +1,15 @@
+import { SQLConnection } from './connection';
+
+/**
+ * The tables in the database. This is a view of the `information_schema.tables` table.
+ */
+export interface PostgreSQLTable {
+ table_name: string;
+}
+
+export const listTables = async (
+ connection: SQLConnection,
+): Promise =>
+ connection.query(
+ `SELECT table_name FROM information_schema.tables WHERE table_catalog = current_database() AND table_schema = current_schema()`,
+ );
diff --git a/packages/sql-store/src/store.ts b/packages/postgresql-store/src/store.ts
similarity index 61%
rename from packages/sql-store/src/store.ts
rename to packages/postgresql-store/src/store.ts
index 48b168d..c51eb15 100644
--- a/packages/sql-store/src/store.ts
+++ b/packages/postgresql-store/src/store.ts
@@ -1,3 +1,11 @@
+import { Pool, PoolConfig } from 'pg';
+import {
+ SQLConnection,
+ dataTypeMap,
+ listTableColumns,
+ listIndexAttributes,
+ listTables,
+} from './queries';
import {
Store,
StoreCollection,
@@ -14,45 +22,41 @@ import {
StoreMutationResponse,
StoreUpdateOptions,
} from '@neuledge/store';
-import {
- SQLConnection,
- listTableColumns,
- listTableStatistics,
- listTables,
-} from './queries';
-import {
- toStoreCollection_Slim,
- toStoreField,
- toStoreIndexes,
-} from './mappers';
+import { getCollection, getStoreCollections } from '@neuledge/sql-store';
-export class SQLStore implements Store {
- constructor(public readonly connection: SQLConnection) {}
+export type PostgreSQLStoreOptions = PoolConfig;
+
+export class PostgreSQLStore implements Store {
+ private pool: Pool;
+ private connection: SQLConnection;
+
+ constructor(options: PostgreSQLStoreOptions) {
+ this.pool = new Pool(options);
+ this.connection = this.pool;
+ }
+
+ // connection methods
+
+ async close(): Promise {
+ await this.pool.end();
+ }
+
+ // store methods
async listCollections(): Promise {
- const tables = await listTables(this.connection);
- return tables.map((table) => toStoreCollection_Slim(table));
+ return getStoreCollections(listTables, this.connection);
}
async describeCollection(
options: StoreDescribeCollectionOptions,
): Promise {
- const { name } = options.collection;
-
- const [columns, statistics] = await Promise.all([
- listTableColumns(this.connection, name),
- listTableStatistics(this.connection, name),
- ]);
-
- const fields = columns.map((column) => toStoreField(column));
- const indexes = toStoreIndexes(statistics);
-
- return {
- name,
- primaryKey: indexes[0],
- indexes: Object.fromEntries(indexes.map((index) => [index.name, index])),
- fields: Object.fromEntries(fields.map((field) => [field.name, field])),
- };
+ return getCollection(
+ options,
+ listTableColumns,
+ listIndexAttributes,
+ dataTypeMap,
+ this.connection,
+ );
}
ensureCollection(options: StoreEnsureCollectionOptions): Promise {
diff --git a/packages/postgresql-store/tsconfig.json b/packages/postgresql-store/tsconfig.json
new file mode 100644
index 0000000..c67724d
--- /dev/null
+++ b/packages/postgresql-store/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "@neuledge/tsconfig/base.json",
+ "compilerOptions": {
+ "baseUrl": "src",
+ "rootDir": "src",
+ "outDir": "dist"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "**/__ignore__/**"]
+}
diff --git a/packages/postgresql-store/tsup.config.json b/packages/postgresql-store/tsup.config.json
new file mode 100644
index 0000000..2f3a43d
--- /dev/null
+++ b/packages/postgresql-store/tsup.config.json
@@ -0,0 +1,6 @@
+{
+ "entry": ["src/index.ts"],
+ "format": ["esm", "cjs"],
+ "sourcemap": true,
+ "shims": true
+}
diff --git a/packages/sql-store/README.md b/packages/sql-store/README.md
index 79302fe..932c199 100644
--- a/packages/sql-store/README.md
+++ b/packages/sql-store/README.md
@@ -12,19 +12,15 @@ npm install @neuledge/sql-store
## 🚀 Getting started
+Import the util functions you need and use them to create your own store:
+
```ts
-import { SQLStore } from '@neuledge/mysql-store';
+import { Store } from '@neuledge/engine';
+import { ... } from '@neuledge/sql-store';
-// create a connection to your SQL database somehow
-const connection = createPool({
+export class MyStore implements Store {
// ...
-});
-
-const store = new SQLStore(connection);
-
-const engine = new Engine({
- store,
-});
+}
```
## 📄 License
diff --git a/packages/sql-store/src/index.ts b/packages/sql-store/src/index.ts
index d406816..ef390d6 100644
--- a/packages/sql-store/src/index.ts
+++ b/packages/sql-store/src/index.ts
@@ -1 +1,2 @@
-export * from './store';
+export * from './logic';
+export * from './mappers';
diff --git a/packages/sql-store/src/logic/collections.ts b/packages/sql-store/src/logic/collections.ts
new file mode 100644
index 0000000..38abaec
--- /dev/null
+++ b/packages/sql-store/src/logic/collections.ts
@@ -0,0 +1,115 @@
+import {
+ SQLColumn,
+ SQLIndexAttribute,
+ SQLIndexColumn,
+ SQLTable,
+ toStoreCollection_Slim,
+ toStoreField,
+ toStoreIndex,
+} from '@/mappers';
+import {
+ StoreCollection,
+ StoreCollection_Slim,
+ StoreDescribeCollectionOptions,
+ StoreError,
+ StoreShapeType,
+} from '@neuledge/store';
+
+export const getStoreCollections = async <
+ A extends unknown[],
+ T extends SQLTable,
+>(
+ listTables: (...args: A) => Promise,
+ ...params: A
+): Promise => {
+ const tables = await listTables(...params);
+ return tables.map((table) => toStoreCollection_Slim(table));
+};
+
+export const getCollection = async <
+ A extends unknown[],
+ C extends SQLColumn,
+ I extends SQLIndexAttribute & Omit,
+>(
+ options: StoreDescribeCollectionOptions,
+ listTableColumns: (name: string, ...args: A) => Promise,
+ listIndexAttributes: (name: string, ...args: A) => Promise,
+ dataTypeMap: Record,
+ ...params: A
+): Promise => {
+ const { name } = options.collection;
+
+ const [columns, indexAttributes] = await Promise.all([
+ listTableColumns(name, ...params),
+ listIndexAttributes(name, ...params),
+ ]);
+
+ const columnMap = Object.fromEntries(
+ columns.map((column) => [column.column_name, column]),
+ );
+
+ const fields = Object.fromEntries(
+ columns.map((column) => [
+ column.column_name,
+ toStoreField(dataTypeMap, column),
+ ]),
+ );
+
+ const indexColumns = groupIndexColumns(columnMap, indexAttributes);
+
+ let primaryKey: string | undefined;
+ const indexes = Object.fromEntries(
+ indexColumns.map((columns) => {
+ const index = toStoreIndex(columns);
+ if (index.unique === 'primary') {
+ primaryKey = index.name;
+ }
+
+ return [index.name, index];
+ }),
+ );
+
+ if (!primaryKey) {
+ throw new StoreError(
+ StoreError.Code.INVALID_DATA,
+ `Primary key not found for collection "${name}"`,
+ );
+ }
+
+ return {
+ name,
+ primaryKey: indexes[primaryKey] as StoreCollection['primaryKey'],
+ indexes,
+ fields,
+ };
+};
+
+const groupIndexColumns = <
+ C extends SQLColumn,
+ I extends SQLIndexAttribute & Omit,
+>(
+ columnMap: Record,
+ indexAttributes: I[],
+): SQLIndexColumn[][] => {
+ const groupMap: Record = {};
+
+ for (const statistic of indexAttributes) {
+ let group = groupMap[statistic.index_name];
+ if (!group) {
+ group = [];
+ groupMap[statistic.index_name] = group;
+ }
+
+ const column = columnMap[statistic.column_name];
+ if (!column) {
+ throw new StoreError(
+ StoreError.Code.INVALID_DATA,
+ `Column "${statistic.column_name}" not found for index "${statistic.index_name}"`,
+ );
+ }
+
+ group.push({ ...column, ...statistic } as never);
+ }
+
+ return Object.values(groupMap);
+};
diff --git a/packages/sql-store/src/logic/index.ts b/packages/sql-store/src/logic/index.ts
new file mode 100644
index 0000000..3eee1ab
--- /dev/null
+++ b/packages/sql-store/src/logic/index.ts
@@ -0,0 +1 @@
+export * from './collections';
diff --git a/packages/sql-store/src/mappers/collection.ts b/packages/sql-store/src/mappers/collection.ts
index dac9b35..1eae8f4 100644
--- a/packages/sql-store/src/mappers/collection.ts
+++ b/packages/sql-store/src/mappers/collection.ts
@@ -1,5 +1,8 @@
import { StoreCollection_Slim } from '@neuledge/store';
-import { SQLTable } from '@/queries';
+
+export interface SQLTable {
+ table_name: string;
+}
export const toStoreCollection_Slim = (
table: SQLTable,
diff --git a/packages/sql-store/src/mappers/field.ts b/packages/sql-store/src/mappers/field.ts
index 31992f6..c28b8f5 100644
--- a/packages/sql-store/src/mappers/field.ts
+++ b/packages/sql-store/src/mappers/field.ts
@@ -1,28 +1,18 @@
import { StoreError, StoreField, StoreShapeType } from '@neuledge/store';
-import { SQLColumn } from '@/queries';
-/**
- * Map the SQL data types to the corresponding StoreShapeType
- */
-const dataTypeMap: Record = {
- varchar: 'string',
- char: 'string',
- text: 'string',
- numeric: 'number',
- decimal: 'number',
- float: 'number',
- double: 'number',
- integer: 'number',
- bigint: 'number',
- boolean: 'boolean',
- bytea: 'binary',
- timestamp: 'date-time',
- timestamptz: 'date-time',
- json: 'json',
- jsonb: 'json',
-};
+export interface SQLColumn {
+ column_name: string;
+ data_type: string;
+ character_maximum_length: number | null;
+ numeric_precision: number | null;
+ numeric_scale: number | null;
+ is_nullable: boolean | 1 | 0;
+}
-export const toStoreField = (column: SQLColumn): StoreField => {
+export const toStoreField = (
+ dataTypeMap: Record,
+ column: SQLColumn,
+): StoreField => {
const type = dataTypeMap[column.data_type];
if (!type) {
throw new StoreError(
@@ -34,7 +24,7 @@ export const toStoreField = (column: SQLColumn): StoreField => {
return {
name: column.column_name,
type,
- nullable: column.is_nullable === 'YES',
+ nullable: !!column.is_nullable,
size: column.character_maximum_length,
precision: column.numeric_precision,
scale: column.numeric_scale,
diff --git a/packages/sql-store/src/mappers/store-index.test.ts b/packages/sql-store/src/mappers/store-index.test.ts
index 1904adb..0129e5d 100644
--- a/packages/sql-store/src/mappers/store-index.test.ts
+++ b/packages/sql-store/src/mappers/store-index.test.ts
@@ -1,27 +1,28 @@
-import { toStoreIndexes } from './store-index';
+import { toStoreIndex } from './store-index';
describe('mappers/store-index', () => {
- describe('toStoreIndexes()', () => {
+ describe('toStoreIndex()', () => {
it('should convert a single primary index', () => {
expect(
- toStoreIndexes([
+ toStoreIndex([
{
- index_name: 'PRIMARY',
- non_unique: 0,
+ index_name: 'id_index',
column_name: 'field_name',
seq_in_index: 1,
- collation: 'A',
+ direction: 'ASC',
+ is_unique: true,
+ is_primary: true,
+ is_auto_increment: true,
},
]),
- ).toEqual([
- {
- name: 'PRIMARY',
- unique: 'primary',
- fields: {
- field_name: { sort: 'asc' },
- },
+ ).toEqual({
+ name: 'id_index',
+ unique: 'primary',
+ auto: 'increment',
+ fields: {
+ field_name: { sort: 'asc' },
},
- ]);
+ });
});
});
});
diff --git a/packages/sql-store/src/mappers/store-index.ts b/packages/sql-store/src/mappers/store-index.ts
index 7cad662..d69b25f 100644
--- a/packages/sql-store/src/mappers/store-index.ts
+++ b/packages/sql-store/src/mappers/store-index.ts
@@ -1,76 +1,45 @@
-import { SQLStatistic } from '@/queries';
-import { StoreError, StoreIndex, StorePrimaryKey } from '@neuledge/store';
-
-export const toStoreIndexes = (
- statistics: SQLStatistic[],
-): [primary: StorePrimaryKey, ...indexes: StoreIndex[]] => {
- const indexStatistics = groupTableStatistics(statistics);
-
- let primaryKey: StorePrimaryKey | undefined;
- const indexes: StoreIndex[] = [];
-
- for (const indexColumn of indexStatistics) {
- const index = toStoreIndex(indexColumn);
-
- if (index.unique === 'primary') {
- primaryKey = index as StorePrimaryKey;
- } else {
- indexes.push(index);
- }
- }
-
- if (!primaryKey) {
- throw new StoreError(
- StoreError.Code.INVALID_DATA,
- `Primary key not found for collection "${name}"`,
- );
- }
-
- return [primaryKey, ...indexes];
-};
-
-const toStoreIndex = (
- indexStatistics: SQLStatistic[],
+import { StoreIndex, StorePrimaryKey } from '@neuledge/store';
+
+export interface SQLIndexAttribute {
+ index_name: string;
+ column_name: string;
+ seq_in_index: number;
+ direction: 'ASC' | 'DESC';
+ is_unique: boolean | 1 | 0;
+}
+
+export interface SQLIndexColumn extends SQLIndexAttribute {
+ is_primary: boolean | 1 | 0;
+ is_auto_increment: boolean | 1 | 0;
+}
+
+export const toStoreIndex = (
+ indexColumns: SQLIndexColumn[],
): StoreIndex | StorePrimaryKey => {
- const { index_name, non_unique, column_extra } = indexStatistics[0];
+ const { index_name, is_unique, is_primary, is_auto_increment } =
+ indexColumns[0];
const index: StoreIndex | StorePrimaryKey = {
name: index_name,
- unique: !non_unique,
+ unique: !!is_unique,
fields: {},
};
- if (index.unique && index_name === 'PRIMARY') {
+ if (index.unique && is_primary) {
index.unique = 'primary';
- if (column_extra === 'auto_increment') {
+ if (is_auto_increment) {
(index as StorePrimaryKey).auto = 'increment';
}
}
- indexStatistics.sort((a, b) => a.seq_in_index - b.seq_in_index);
+ indexColumns.sort((a, b) => a.seq_in_index - b.seq_in_index);
- for (const statistic of indexStatistics) {
+ for (const statistic of indexColumns) {
index.fields[statistic.column_name] = {
- sort: statistic.collation === 'A' ? 'asc' : 'desc',
+ sort: statistic.direction === 'ASC' ? 'asc' : 'desc',
};
}
return index;
};
-
-const groupTableStatistics = (statistics: SQLStatistic[]): SQLStatistic[][] => {
- const groupMap: Record = {};
-
- for (const statistic of statistics) {
- let group = groupMap[statistic.index_name];
- if (!group) {
- group = [];
- groupMap[statistic.index_name] = group;
- }
-
- group.push(statistic);
- }
-
- return Object.values(groupMap);
-};
diff --git a/packages/sql-store/src/queries/list-table-columns.ts b/packages/sql-store/src/queries/list-table-columns.ts
deleted file mode 100644
index a52d591..0000000
--- a/packages/sql-store/src/queries/list-table-columns.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { SQLConnection } from './connection';
-
-/**
- * A table column from the information_schema.columns table.
- */
-export interface SQLColumn {
- column_name: string;
- data_type: string;
- character_maximum_length?: number | null;
- numeric_precision?: number | null;
- numeric_scale?: number | null;
- is_nullable: 'YES' | 'NO';
-}
-
-export const listTableColumns = async (
- connection: SQLConnection,
- tableName: string,
-): Promise =>
- connection.query(
- `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, is_nullable
-FROM information_schema.columns
-WHERE table_name = ? AND table_schema = DATABASE()`,
- [tableName],
- );
diff --git a/packages/sql-store/src/queries/list-table-statistics.ts b/packages/sql-store/src/queries/list-table-statistics.ts
deleted file mode 100644
index f47560c..0000000
--- a/packages/sql-store/src/queries/list-table-statistics.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { SQLConnection } from './connection';
-
-/**
- * A table statistic row from the information_schema.statistics table.
- */
-export interface SQLStatistic {
- index_name: string;
- column_name: string;
- seq_in_index: number;
- collation: 'A' | 'D';
- non_unique: number;
- column_extra?: string;
-}
-
-export const listTableStatistics = async (
- connection: SQLConnection,
- tableName: string,
-): Promise =>
- connection.query(
- `SELECT index_name, column_name, seq_in_index, collation, non_unique, extra AS column_extra
- FROM information_schema.statistics INNER JOIN information_schema.columns USING (table_schema, table_name, column_name)
- WHERE table_schema = DATABASE() AND table_name = ?
- ORDER BY index_name, seq_in_index`,
- [tableName],
- );
diff --git a/yarn.lock b/yarn.lock
index b0c2102..c83e1c3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2007,6 +2007,15 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
+"@types/pg@^8.6.6":
+ version "8.6.6"
+ resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.6.tgz#21cdf873a3e345a6e78f394677e3b3b1b543cb80"
+ integrity sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==
+ dependencies:
+ "@types/node" "*"
+ pg-protocol "*"
+ pg-types "^2.2.0"
+
"@types/pluralize@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c"
@@ -2655,6 +2664,11 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+buffer-writer@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04"
+ integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==
+
buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
@@ -6021,6 +6035,11 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+packet-reader@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74"
+ integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==
+
param-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
@@ -6143,6 +6162,57 @@ pend@~1.2.0:
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
+pg-connection-string@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
+ integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
+
+pg-int8@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
+ integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
+
+pg-pool@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e"
+ integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==
+
+pg-protocol@*, pg-protocol@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833"
+ integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==
+
+pg-types@^2.1.0, pg-types@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
+ integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
+ dependencies:
+ pg-int8 "1.0.1"
+ postgres-array "~2.0.0"
+ postgres-bytea "~1.0.0"
+ postgres-date "~1.0.4"
+ postgres-interval "^1.1.0"
+
+pg@^8.10.0:
+ version "8.10.0"
+ resolved "https://registry.yarnpkg.com/pg/-/pg-8.10.0.tgz#5b8379c9b4a36451d110fc8cd98fc325fe62ad24"
+ integrity sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==
+ dependencies:
+ buffer-writer "2.0.0"
+ packet-reader "1.0.0"
+ pg-connection-string "^2.5.0"
+ pg-pool "^3.6.0"
+ pg-protocol "^1.6.0"
+ pg-types "^2.1.0"
+ pgpass "1.x"
+
+pgpass@1.x:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d"
+ integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==
+ dependencies:
+ split2 "^4.1.0"
+
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
@@ -6220,6 +6290,28 @@ postcss-load-config@^3.0.1:
lilconfig "^2.0.5"
yaml "^1.10.2"
+postgres-array@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
+ integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==
+
+postgres-bytea@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
+ integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==
+
+postgres-date@~1.0.4:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8"
+ integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==
+
+postgres-interval@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695"
+ integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==
+ dependencies:
+ xtend "^4.0.0"
+
prebuild-install@^7.0.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
@@ -6960,6 +7052,11 @@ split2@^4.0.0:
resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809"
integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==
+split2@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
+ integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
+
sponge-case@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sponge-case/-/sponge-case-1.0.1.tgz#260833b86453883d974f84854cdb63aecc5aef4c"
@@ -7423,47 +7520,47 @@ tunnel@0.0.6:
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
-turbo-darwin-64@1.8.5:
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.8.5.tgz#6fdd2e9e2b8afead04e17380fc222863794e6007"
- integrity sha512-CAYh56bzeHfnh7jTm03r29bh8p5a/EjQo1Id5yLUH7hS7msTau/+YpxJWPodLbN0UQsUYivUqHQkglJ+eMJ7xA==
-
-turbo-darwin-arm64@1.8.5:
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.8.5.tgz#8d72d67a87b6d247565de79477b0c7aed6a83c2b"
- integrity sha512-R3jCPOv+lu3dcvMhj8b/Defv6dyUwX6W+tbX7d6YUCA46Plf/bGCQ8+MSbxmr/4E1GyGOVFsn1wRfiYk0us/Dg==
-
-turbo-linux-64@1.8.5:
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.8.5.tgz#a080015aa1c725604637a743a5b878d100aaf88b"
- integrity sha512-YRc/KNRZeUVvth11UO4SDQZR2IqGgl9MSsbzqoHuFz4B4Q5QXH7onHogv9aXWE/BZBBbcrSBTlwBSG0Gg+J8hg==
-
-turbo-linux-arm64@1.8.5:
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.8.5.tgz#96fa915f10e81a16eccf74e122e96fcba4e132e0"
- integrity sha512-8exVZb7XBl/V3gHSweuUyG2D9IzfWqwLvlXoeLWlVYSj61Ajgdv+WU7lvUmx+H2s+sSKqmIFmewA5Lw6YY37sg==
-
-turbo-windows-64@1.8.5:
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.8.5.tgz#14ca1a577e982c34fd606fa6499eaf4fea0eca36"
- integrity sha512-fA8PU5ZNoFnQkapG06WiEqfsVQ5wbIPkIqTwUsd/M2Lp+KgxE79SQbuEI+2vQ9SmwM5qoMi515IPjgvXAJXgCw==
-
-turbo-windows-arm64@1.8.5:
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.8.5.tgz#286c7aacd8c9e2a8a0f5ab767268aadc8f6c7955"
- integrity sha512-SW/NvIdhckLsAWjU/iqBbCB0S8kXupKscUK3kEW1DZIr3MYcP/yIuaE/IdPuqcoF3VP0I3TLD4VTYCCKAo3tKA==
-
-turbo@^1.8.3:
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.8.5.tgz#933413257783ede75471b8ebf2435ebc7be50ad7"
- integrity sha512-UBnH2wIFb5g6OQCk8f34Ud15ZXV4xEMmugeDJTU5Ur2LpVRsNEny0isSCYdb3Iu3howoNyyXmtpaxWsAwNYkkg==
+turbo-darwin-64@1.8.8:
+ version "1.8.8"
+ resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.8.8.tgz#f72b1b6275415b17238f450032c8ef5e5fc71777"
+ integrity sha512-18cSeIm7aeEvIxGyq7PVoFyEnPpWDM/0CpZvXKHpQ6qMTkfNt517qVqUTAwsIYqNS8xazcKAqkNbvU1V49n65Q==
+
+turbo-darwin-arm64@1.8.8:
+ version "1.8.8"
+ resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.8.8.tgz#8ec78848e0d5978fd732b3588a1b406fdb978839"
+ integrity sha512-ruGRI9nHxojIGLQv1TPgN7ud4HO4V8mFBwSgO6oDoZTNuk5ybWybItGR+yu6fni5vJoyMHXOYA2srnxvOc7hjQ==
+
+turbo-linux-64@1.8.8:
+ version "1.8.8"
+ resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.8.8.tgz#b1f707b23bc6e22b2894dd8063fc2fa4dbb6ffb9"
+ integrity sha512-N/GkHTHeIQogXB1/6ZWfxHx+ubYeb8Jlq3b/3jnU4zLucpZzTQ8XkXIAfJG/TL3Q7ON7xQ8yGOyGLhHL7MpFRg==
+
+turbo-linux-arm64@1.8.8:
+ version "1.8.8"
+ resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.8.8.tgz#34575bdffd2af8c835d9ba3dd9e3a83e0d31dac9"
+ integrity sha512-hKqLbBHgUkYf2Ww8uBL9UYdBFQ5677a7QXdsFhONXoACbDUPvpK4BKlz3NN7G4NZ+g9dGju+OJJjQP0VXRHb5w==
+
+turbo-windows-64@1.8.8:
+ version "1.8.8"
+ resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.8.8.tgz#73f67969d54269c95cbf7f082e22c20368aedddc"
+ integrity sha512-2ndjDJyzkNslXxLt+PQuU21AHJWc8f6MnLypXy3KsN4EyX/uKKGZS0QJWz27PeHg0JS75PVvhfFV+L9t9i+Yyg==
+
+turbo-windows-arm64@1.8.8:
+ version "1.8.8"
+ resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.8.8.tgz#c80b9a170adf6ee028e9dcae45b07755af83f3f2"
+ integrity sha512-xCA3oxgmW9OMqpI34AAmKfOVsfDljhD5YBwgs0ZDsn5h3kCHhC4x9W5dDk1oyQ4F5EXSH3xVym5/xl1J6WRpUg==
+
+turbo@^1.8.8:
+ version "1.8.8"
+ resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.8.8.tgz#8bb331e3f0bd9656b20321339e91e899ad499012"
+ integrity sha512-qYJ5NjoTX+591/x09KgsDOPVDUJfU9GoS+6jszQQlLp1AHrf1wRFA3Yps8U+/HTG03q0M4qouOfOLtRQP4QypA==
optionalDependencies:
- turbo-darwin-64 "1.8.5"
- turbo-darwin-arm64 "1.8.5"
- turbo-linux-64 "1.8.5"
- turbo-linux-arm64 "1.8.5"
- turbo-windows-64 "1.8.5"
- turbo-windows-arm64 "1.8.5"
+ turbo-darwin-64 "1.8.8"
+ turbo-darwin-arm64 "1.8.8"
+ turbo-linux-64 "1.8.8"
+ turbo-linux-arm64 "1.8.8"
+ turbo-windows-64 "1.8.8"
+ turbo-windows-arm64 "1.8.8"
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
@@ -7831,7 +7928,7 @@ xmlbuilder@~11.0.0:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
-xtend@^4.0.2:
+xtend@^4.0.0, xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
From d88f010b78e5a7f0e3c8a56dd188db280ca7983d Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Sun, 2 Apr 2023 19:53:34 +0300
Subject: [PATCH 06/24] supports store dropCollection
---
packages/mysql-store/src/queries/index.ts | 1 -
.../src/queries/list-table-columns.ts | 2 +-
.../src/queries/list-table-statistics.ts | 2 +-
.../mysql-store/src/queries/list-tables.ts | 2 +-
packages/mysql-store/src/store.ts | 22 +++++++++++--------
.../src/queries/connection.ts | 3 ---
.../postgresql-store/src/queries/index.ts | 1 -
.../src/queries/list-table-columns.ts | 2 +-
.../src/queries/list-table-statistics.ts | 2 +-
.../src/queries/list-tables.ts | 2 +-
packages/postgresql-store/src/store.ts | 22 +++++++++++--------
packages/sql-store/src/index.ts | 1 +
packages/sql-store/src/logic/collections.ts | 9 ++++++++
.../src/queries/connection.ts | 0
packages/sql-store/src/queries/drop-table.ts | 6 +++++
packages/sql-store/src/queries/index.ts | 2 ++
16 files changed, 50 insertions(+), 29 deletions(-)
delete mode 100644 packages/postgresql-store/src/queries/connection.ts
rename packages/{mysql-store => sql-store}/src/queries/connection.ts (100%)
create mode 100644 packages/sql-store/src/queries/drop-table.ts
create mode 100644 packages/sql-store/src/queries/index.ts
diff --git a/packages/mysql-store/src/queries/index.ts b/packages/mysql-store/src/queries/index.ts
index 45599ce..78749e0 100644
--- a/packages/mysql-store/src/queries/index.ts
+++ b/packages/mysql-store/src/queries/index.ts
@@ -1,4 +1,3 @@
-export * from './connection';
export * from './list-tables';
export * from './list-table-columns';
export * from './list-table-statistics';
diff --git a/packages/mysql-store/src/queries/list-table-columns.ts b/packages/mysql-store/src/queries/list-table-columns.ts
index 6c3ade8..3e97bba 100644
--- a/packages/mysql-store/src/queries/list-table-columns.ts
+++ b/packages/mysql-store/src/queries/list-table-columns.ts
@@ -1,5 +1,5 @@
import { StoreShapeType } from '@neuledge/store';
-import { SQLConnection } from './connection';
+import { SQLConnection } from '@neuledge/sql-store';
/**
* A table column from the information_schema.columns table.
diff --git a/packages/mysql-store/src/queries/list-table-statistics.ts b/packages/mysql-store/src/queries/list-table-statistics.ts
index 5f3ad45..fb1509e 100644
--- a/packages/mysql-store/src/queries/list-table-statistics.ts
+++ b/packages/mysql-store/src/queries/list-table-statistics.ts
@@ -1,4 +1,4 @@
-import { SQLConnection } from './connection';
+import { SQLConnection } from '@neuledge/sql-store';
/**
* A table statistic row from the information_schema.statistics table.
diff --git a/packages/mysql-store/src/queries/list-tables.ts b/packages/mysql-store/src/queries/list-tables.ts
index 828fbbc..23fdb8f 100644
--- a/packages/mysql-store/src/queries/list-tables.ts
+++ b/packages/mysql-store/src/queries/list-tables.ts
@@ -1,4 +1,4 @@
-import { SQLConnection } from './connection';
+import { SQLConnection } from '@neuledge/sql-store';
/**
* The tables in the database. This is a view of the `information_schema.tables` table.
diff --git a/packages/mysql-store/src/store.ts b/packages/mysql-store/src/store.ts
index f51c672..77ee271 100644
--- a/packages/mysql-store/src/store.ts
+++ b/packages/mysql-store/src/store.ts
@@ -16,13 +16,17 @@ import {
StoreUpdateOptions,
} from '@neuledge/store';
import {
- SQLConnection,
dataTypeMap,
listTableColumns,
listIndexAttributes,
listTables,
} from './queries';
-import { getCollection, getStoreCollections } from '@neuledge/sql-store';
+import {
+ SQLConnection,
+ dropCollection,
+ getCollection,
+ getStoreCollections,
+} from '@neuledge/sql-store';
export type MySQLStoreOptions = PoolConfig;
@@ -69,27 +73,27 @@ export class MySQLStore implements Store {
);
}
- ensureCollection(options: StoreEnsureCollectionOptions): Promise {
+ async ensureCollection(options: StoreEnsureCollectionOptions): Promise {
throw new Error('Method not implemented.');
}
- dropCollection(options: StoreDropCollectionOptions): Promise {
- throw new Error('Method not implemented.');
+ async dropCollection(options: StoreDropCollectionOptions): Promise {
+ return dropCollection(options, this.connection);
}
- find(options: StoreFindOptions): Promise> {
+ async find(options: StoreFindOptions): Promise> {
throw new Error('Method not implemented.');
}
- insert(options: StoreInsertOptions): Promise {
+ async insert(options: StoreInsertOptions): Promise {
throw new Error('Method not implemented.');
}
- update(options: StoreUpdateOptions): Promise {
+ async update(options: StoreUpdateOptions): Promise {
throw new Error('Method not implemented.');
}
- delete(options: StoreDeleteOptions): Promise {
+ async delete(options: StoreDeleteOptions): Promise {
throw new Error('Method not implemented.');
}
}
diff --git a/packages/postgresql-store/src/queries/connection.ts b/packages/postgresql-store/src/queries/connection.ts
deleted file mode 100644
index 5176269..0000000
--- a/packages/postgresql-store/src/queries/connection.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface SQLConnection {
- query(sql: string, params?: unknown[]): Promise;
-}
diff --git a/packages/postgresql-store/src/queries/index.ts b/packages/postgresql-store/src/queries/index.ts
index 45599ce..78749e0 100644
--- a/packages/postgresql-store/src/queries/index.ts
+++ b/packages/postgresql-store/src/queries/index.ts
@@ -1,4 +1,3 @@
-export * from './connection';
export * from './list-tables';
export * from './list-table-columns';
export * from './list-table-statistics';
diff --git a/packages/postgresql-store/src/queries/list-table-columns.ts b/packages/postgresql-store/src/queries/list-table-columns.ts
index 869e950..7453fea 100644
--- a/packages/postgresql-store/src/queries/list-table-columns.ts
+++ b/packages/postgresql-store/src/queries/list-table-columns.ts
@@ -1,5 +1,5 @@
import { StoreShapeType } from '@neuledge/store';
-import { SQLConnection } from './connection';
+import { SQLConnection } from '@neuledge/sql-store';
/**
* A table column from the information_schema.columns table.
diff --git a/packages/postgresql-store/src/queries/list-table-statistics.ts b/packages/postgresql-store/src/queries/list-table-statistics.ts
index 150bb55..325f8b3 100644
--- a/packages/postgresql-store/src/queries/list-table-statistics.ts
+++ b/packages/postgresql-store/src/queries/list-table-statistics.ts
@@ -1,4 +1,4 @@
-import { SQLConnection } from './connection';
+import { SQLConnection } from '@neuledge/sql-store';
/**
* A table statistic row from the information_schema.statistics table.
diff --git a/packages/postgresql-store/src/queries/list-tables.ts b/packages/postgresql-store/src/queries/list-tables.ts
index bb4e5c5..933bc0e 100644
--- a/packages/postgresql-store/src/queries/list-tables.ts
+++ b/packages/postgresql-store/src/queries/list-tables.ts
@@ -1,4 +1,4 @@
-import { SQLConnection } from './connection';
+import { SQLConnection } from '@neuledge/sql-store';
/**
* The tables in the database. This is a view of the `information_schema.tables` table.
diff --git a/packages/postgresql-store/src/store.ts b/packages/postgresql-store/src/store.ts
index c51eb15..17649da 100644
--- a/packages/postgresql-store/src/store.ts
+++ b/packages/postgresql-store/src/store.ts
@@ -1,6 +1,5 @@
import { Pool, PoolConfig } from 'pg';
import {
- SQLConnection,
dataTypeMap,
listTableColumns,
listIndexAttributes,
@@ -22,7 +21,12 @@ import {
StoreMutationResponse,
StoreUpdateOptions,
} from '@neuledge/store';
-import { getCollection, getStoreCollections } from '@neuledge/sql-store';
+import {
+ SQLConnection,
+ dropCollection,
+ getCollection,
+ getStoreCollections,
+} from '@neuledge/sql-store';
export type PostgreSQLStoreOptions = PoolConfig;
@@ -59,27 +63,27 @@ export class PostgreSQLStore implements Store {
);
}
- ensureCollection(options: StoreEnsureCollectionOptions): Promise {
+ async ensureCollection(options: StoreEnsureCollectionOptions): Promise {
throw new Error('Method not implemented.');
}
- dropCollection(options: StoreDropCollectionOptions): Promise {
- throw new Error('Method not implemented.');
+ async dropCollection(options: StoreDropCollectionOptions): Promise {
+ return dropCollection(options, this.connection);
}
- find(options: StoreFindOptions): Promise> {
+ async find(options: StoreFindOptions): Promise> {
throw new Error('Method not implemented.');
}
- insert(options: StoreInsertOptions): Promise {
+ async insert(options: StoreInsertOptions): Promise {
throw new Error('Method not implemented.');
}
- update(options: StoreUpdateOptions): Promise {
+ async update(options: StoreUpdateOptions): Promise {
throw new Error('Method not implemented.');
}
- delete(options: StoreDeleteOptions): Promise {
+ async delete(options: StoreDeleteOptions): Promise {
throw new Error('Method not implemented.');
}
}
diff --git a/packages/sql-store/src/index.ts b/packages/sql-store/src/index.ts
index ef390d6..d941abe 100644
--- a/packages/sql-store/src/index.ts
+++ b/packages/sql-store/src/index.ts
@@ -1,2 +1,3 @@
export * from './logic';
export * from './mappers';
+export * from './queries';
diff --git a/packages/sql-store/src/logic/collections.ts b/packages/sql-store/src/logic/collections.ts
index 38abaec..87abb6f 100644
--- a/packages/sql-store/src/logic/collections.ts
+++ b/packages/sql-store/src/logic/collections.ts
@@ -7,10 +7,12 @@ import {
toStoreField,
toStoreIndex,
} from '@/mappers';
+import { SQLConnection, dropTableIfExists } from '@/queries';
import {
StoreCollection,
StoreCollection_Slim,
StoreDescribeCollectionOptions,
+ StoreDropCollectionOptions,
StoreError,
StoreShapeType,
} from '@neuledge/store';
@@ -113,3 +115,10 @@ const groupIndexColumns = <
return Object.values(groupMap);
};
+
+export const dropCollection = async (
+ options: StoreDropCollectionOptions,
+ connection: SQLConnection,
+): Promise => {
+ await dropTableIfExists(connection, options.collection.name);
+};
diff --git a/packages/mysql-store/src/queries/connection.ts b/packages/sql-store/src/queries/connection.ts
similarity index 100%
rename from packages/mysql-store/src/queries/connection.ts
rename to packages/sql-store/src/queries/connection.ts
diff --git a/packages/sql-store/src/queries/drop-table.ts b/packages/sql-store/src/queries/drop-table.ts
new file mode 100644
index 0000000..a6a0689
--- /dev/null
+++ b/packages/sql-store/src/queries/drop-table.ts
@@ -0,0 +1,6 @@
+import { SQLConnection } from './connection';
+
+export const dropTableIfExists = async (
+ connection: SQLConnection,
+ tableName: string,
+): Promise => connection.query(`DROP TABLE IF EXISTS ?`, [tableName]);
diff --git a/packages/sql-store/src/queries/index.ts b/packages/sql-store/src/queries/index.ts
new file mode 100644
index 0000000..b2a91d3
--- /dev/null
+++ b/packages/sql-store/src/queries/index.ts
@@ -0,0 +1,2 @@
+export * from './connection';
+export * from './drop-table';
From 7d5b2b91da155bd7acb1513ec9918b85fe516df2 Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Thu, 13 Apr 2023 16:10:50 +0300
Subject: [PATCH 07/24] ensure table
---
package.json | 2 +-
.../mysql-store/src/queries/add-column.ts | 8 ++
packages/mysql-store/src/queries/add-index.ts | 17 ++++
.../mysql-store/src/queries/create-table.ts | 16 +++
packages/mysql-store/src/queries/index.ts | 3 +
packages/mysql-store/src/store.ts | 25 +++--
.../src/queries/add-column.ts | 8 ++
.../postgresql-store/src/queries/add-index.ts | 15 +++
.../src/queries/create-table.ts | 15 +++
.../src/queries/drop-index.ts | 11 +++
.../postgresql-store/src/queries/index.ts | 4 +
packages/postgresql-store/src/store.test.ts | 31 ++++++
packages/postgresql-store/src/store.ts | 41 +++++---
packages/sql-store/package.json | 3 +-
.../describe.ts} | 53 +++++-----
.../sql-store/src/logic/collections/drop.ts | 9 ++
.../sql-store/src/logic/collections/ensure.ts | 98 +++++++++++++++++++
.../sql-store/src/logic/collections/index.ts | 4 +
.../sql-store/src/logic/collections/list.ts | 15 +++
packages/sql-store/src/queries/drop-column.ts | 9 ++
packages/sql-store/src/queries/drop-index.ts | 9 ++
.../sql-store/src/queries/index-columns.ts | 6 ++
packages/sql-store/src/queries/index.ts | 3 +
yarn.lock | 80 +++++++--------
24 files changed, 393 insertions(+), 92 deletions(-)
create mode 100644 packages/mysql-store/src/queries/add-column.ts
create mode 100644 packages/mysql-store/src/queries/add-index.ts
create mode 100644 packages/mysql-store/src/queries/create-table.ts
create mode 100644 packages/postgresql-store/src/queries/add-column.ts
create mode 100644 packages/postgresql-store/src/queries/add-index.ts
create mode 100644 packages/postgresql-store/src/queries/create-table.ts
create mode 100644 packages/postgresql-store/src/queries/drop-index.ts
create mode 100644 packages/postgresql-store/src/store.test.ts
rename packages/sql-store/src/logic/{collections.ts => collections/describe.ts} (69%)
create mode 100644 packages/sql-store/src/logic/collections/drop.ts
create mode 100644 packages/sql-store/src/logic/collections/ensure.ts
create mode 100644 packages/sql-store/src/logic/collections/index.ts
create mode 100644 packages/sql-store/src/logic/collections/list.ts
create mode 100644 packages/sql-store/src/queries/drop-column.ts
create mode 100644 packages/sql-store/src/queries/drop-index.ts
create mode 100644 packages/sql-store/src/queries/index-columns.ts
diff --git a/package.json b/package.json
index 58ebd5f..9ff0097 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"ts-node": "^10.9.1",
"tsc-alias": "^1.8.5",
"tsup": "^6.6.3",
- "turbo": "^1.8.8",
+ "turbo": "^1.9.1",
"typescript": "^5.0.2"
}
}
diff --git a/packages/mysql-store/src/queries/add-column.ts b/packages/mysql-store/src/queries/add-column.ts
new file mode 100644
index 0000000..0526579
--- /dev/null
+++ b/packages/mysql-store/src/queries/add-column.ts
@@ -0,0 +1,8 @@
+import { SQLConnection } from '@neuledge/sql-store';
+import { StoreField } from '@neuledge/store';
+
+export const addColumn = async (
+ tableName: string,
+ field: StoreField,
+ connection: SQLConnection,
+): Promise => {};
diff --git a/packages/mysql-store/src/queries/add-index.ts b/packages/mysql-store/src/queries/add-index.ts
new file mode 100644
index 0000000..aec306f
--- /dev/null
+++ b/packages/mysql-store/src/queries/add-index.ts
@@ -0,0 +1,17 @@
+import { SQLConnection, indexColumns } from '@neuledge/sql-store';
+import { StoreIndex } from '@neuledge/store';
+
+// FIXME handle if not exists on mysql
+
+export const addIndex = async (
+ tableName: string,
+ index: StoreIndex,
+ connection: SQLConnection,
+): Promise => {
+ await connection.query(
+ `CREATE ${
+ index.unique ? 'UNIQUE INDEX' : 'INDEX'
+ } IF NOT EXISTS ? ON ? (${indexColumns(index)})`,
+ [index.name, tableName],
+ );
+};
diff --git a/packages/mysql-store/src/queries/create-table.ts b/packages/mysql-store/src/queries/create-table.ts
new file mode 100644
index 0000000..af8195e
--- /dev/null
+++ b/packages/mysql-store/src/queries/create-table.ts
@@ -0,0 +1,16 @@
+import { SQLConnection } from '@neuledge/sql-store';
+import { StoreCollection } from '@neuledge/store';
+
+// FIXME handle if not exists on mysql
+
+export const createTableIfNotExists = async (
+ collection: StoreCollection,
+ connection: SQLConnection,
+): Promise => {
+ await connection.query(
+ `CREATE TABLE IF NOT EXISTS ? (
+ ${/* FIXME add columns */ ''}
+ )`,
+ [collection.name],
+ );
+};
diff --git a/packages/mysql-store/src/queries/index.ts b/packages/mysql-store/src/queries/index.ts
index 78749e0..45b41a4 100644
--- a/packages/mysql-store/src/queries/index.ts
+++ b/packages/mysql-store/src/queries/index.ts
@@ -1,3 +1,6 @@
+export * from './add-column';
+export * from './add-index';
+export * from './create-table';
export * from './list-tables';
export * from './list-table-columns';
export * from './list-table-statistics';
diff --git a/packages/mysql-store/src/store.ts b/packages/mysql-store/src/store.ts
index 77ee271..922fceb 100644
--- a/packages/mysql-store/src/store.ts
+++ b/packages/mysql-store/src/store.ts
@@ -20,12 +20,16 @@ import {
listTableColumns,
listIndexAttributes,
listTables,
+ createTableIfNotExists,
+ addIndex,
+ addColumn,
} from './queries';
import {
SQLConnection,
+ describeCollection,
dropCollection,
- getCollection,
- getStoreCollections,
+ ensureCollection,
+ listCollections,
} from '@neuledge/sql-store';
export type MySQLStoreOptions = PoolConfig;
@@ -58,23 +62,28 @@ export class MySQLStore implements Store {
// store methods
async listCollections(): Promise {
- return getStoreCollections(listTables, this.connection);
+ return listCollections(this.connection, { listTables });
}
async describeCollection(
options: StoreDescribeCollectionOptions,
): Promise {
- return getCollection(
- options,
+ return describeCollection(options, this.connection, {
listTableColumns,
listIndexAttributes,
dataTypeMap,
- this.connection,
- );
+ });
}
async ensureCollection(options: StoreEnsureCollectionOptions): Promise {
- throw new Error('Method not implemented.');
+ return ensureCollection(options, this.connection, {
+ createTableIfNotExists,
+ addIndex,
+ addColumn,
+ listTableColumns,
+ listIndexAttributes,
+ dataTypeMap,
+ });
}
async dropCollection(options: StoreDropCollectionOptions): Promise {
diff --git a/packages/postgresql-store/src/queries/add-column.ts b/packages/postgresql-store/src/queries/add-column.ts
new file mode 100644
index 0000000..0526579
--- /dev/null
+++ b/packages/postgresql-store/src/queries/add-column.ts
@@ -0,0 +1,8 @@
+import { SQLConnection } from '@neuledge/sql-store';
+import { StoreField } from '@neuledge/store';
+
+export const addColumn = async (
+ tableName: string,
+ field: StoreField,
+ connection: SQLConnection,
+): Promise => {};
diff --git a/packages/postgresql-store/src/queries/add-index.ts b/packages/postgresql-store/src/queries/add-index.ts
new file mode 100644
index 0000000..75dd409
--- /dev/null
+++ b/packages/postgresql-store/src/queries/add-index.ts
@@ -0,0 +1,15 @@
+import { SQLConnection, indexColumns } from '@neuledge/sql-store';
+import { StoreIndex } from '@neuledge/store';
+
+export const addIndex = async (
+ tableName: string,
+ index: StoreIndex,
+ connection: SQLConnection,
+): Promise => {
+ await connection.query(
+ `CREATE ${
+ index.unique ? 'UNIQUE INDEX' : 'INDEX'
+ } IF NOT EXISTS ? (${indexColumns(index)})`,
+ [`${tableName}_${index.name}_idx`],
+ );
+};
diff --git a/packages/postgresql-store/src/queries/create-table.ts b/packages/postgresql-store/src/queries/create-table.ts
new file mode 100644
index 0000000..bb63a12
--- /dev/null
+++ b/packages/postgresql-store/src/queries/create-table.ts
@@ -0,0 +1,15 @@
+import { SQLConnection, indexColumns } from '@neuledge/sql-store';
+import { StoreCollection } from '@neuledge/store';
+
+export const createTableIfNotExists = async (
+ collection: StoreCollection,
+ connection: SQLConnection,
+): Promise => {
+ await connection.query(
+ `CREATE TABLE IF NOT EXISTS ? (
+ ${/* FIXME add columns */ ''}
+ CONSTRAINT ? PRIMARY KEY (${indexColumns(collection.primaryKey)})
+ )`,
+ [collection.name],
+ );
+};
diff --git a/packages/postgresql-store/src/queries/drop-index.ts b/packages/postgresql-store/src/queries/drop-index.ts
new file mode 100644
index 0000000..595fa2d
--- /dev/null
+++ b/packages/postgresql-store/src/queries/drop-index.ts
@@ -0,0 +1,11 @@
+import { SQLConnection } from '@neuledge/sql-store';
+
+export const dropIndex = async (
+ tableName: string,
+ index: string,
+ connection: SQLConnection,
+): Promise => {
+ await connection.query(`DROP INDEX IF EXISTS ?`, [
+ `${tableName}_${index}_idx`,
+ ]);
+};
diff --git a/packages/postgresql-store/src/queries/index.ts b/packages/postgresql-store/src/queries/index.ts
index 78749e0..66fa894 100644
--- a/packages/postgresql-store/src/queries/index.ts
+++ b/packages/postgresql-store/src/queries/index.ts
@@ -1,3 +1,7 @@
+export * from './add-column';
+export * from './add-index';
+export * from './drop-index';
+export * from './create-table';
export * from './list-tables';
export * from './list-table-columns';
export * from './list-table-statistics';
diff --git a/packages/postgresql-store/src/store.test.ts b/packages/postgresql-store/src/store.test.ts
new file mode 100644
index 0000000..944909c
--- /dev/null
+++ b/packages/postgresql-store/src/store.test.ts
@@ -0,0 +1,31 @@
+import { PostgreSQLStore } from './store';
+
+describe('store', () => {
+ describe('PostgreSQLStore()', () => {
+ describe('.constructor()', () => {
+ it('should be able to create a new store', () => {
+ const store = new PostgreSQLStore({
+ pool: {} as never,
+ });
+
+ expect(store).toBeInstanceOf(PostgreSQLStore);
+ });
+ });
+
+ describe('.close()', () => {
+ it('should be able to close the store', async () => {
+ const end = jest.fn();
+
+ const store = new PostgreSQLStore({
+ pool: { end } as never,
+ });
+
+ expect(store).toBeInstanceOf(PostgreSQLStore);
+ expect(end).toHaveBeenCalledTimes(0);
+
+ await store.close();
+ expect(end).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+});
diff --git a/packages/postgresql-store/src/store.ts b/packages/postgresql-store/src/store.ts
index 17649da..b917ada 100644
--- a/packages/postgresql-store/src/store.ts
+++ b/packages/postgresql-store/src/store.ts
@@ -4,6 +4,10 @@ import {
listTableColumns,
listIndexAttributes,
listTables,
+ dropIndex,
+ addIndex,
+ createTableIfNotExists,
+ addColumn,
} from './queries';
import {
Store,
@@ -24,18 +28,27 @@ import {
import {
SQLConnection,
dropCollection,
- getCollection,
- getStoreCollections,
+ describeCollection,
+ listCollections,
+ ensureCollection,
} from '@neuledge/sql-store';
-export type PostgreSQLStoreOptions = PoolConfig;
+export type PostgreSQLStorePool =
+ | (SQLConnection & { end: () => unknown })
+ | Pool;
+
+export type PostgreSQLStoreOptions =
+ | PoolConfig
+ | {
+ pool: PostgreSQLStorePool;
+ };
export class PostgreSQLStore implements Store {
- private pool: Pool;
+ private pool: PostgreSQLStorePool;
private connection: SQLConnection;
constructor(options: PostgreSQLStoreOptions) {
- this.pool = new Pool(options);
+ this.pool = 'pool' in options ? options.pool : new Pool(options);
this.connection = this.pool;
}
@@ -48,23 +61,29 @@ export class PostgreSQLStore implements Store {
// store methods
async listCollections(): Promise {
- return getStoreCollections(listTables, this.connection);
+ return listCollections(this.connection, { listTables });
}
async describeCollection(
options: StoreDescribeCollectionOptions,
): Promise {
- return getCollection(
- options,
+ return describeCollection(options, this.connection, {
listTableColumns,
listIndexAttributes,
dataTypeMap,
- this.connection,
- );
+ });
}
async ensureCollection(options: StoreEnsureCollectionOptions): Promise {
- throw new Error('Method not implemented.');
+ return ensureCollection(options, this.connection, {
+ createTableIfNotExists,
+ addIndex,
+ addColumn,
+ dropIndex,
+ listTableColumns,
+ listIndexAttributes,
+ dataTypeMap,
+ });
}
async dropCollection(options: StoreDropCollectionOptions): Promise {
diff --git a/packages/sql-store/package.json b/packages/sql-store/package.json
index 00f552f..8fd4baa 100644
--- a/packages/sql-store/package.json
+++ b/packages/sql-store/package.json
@@ -38,6 +38,7 @@
"lint:strict": "yarn lint --max-warnings 0"
},
"dependencies": {
- "@neuledge/store": "^0.2.0"
+ "@neuledge/store": "^0.2.0",
+ "p-limit": "^3.1.0"
}
}
diff --git a/packages/sql-store/src/logic/collections.ts b/packages/sql-store/src/logic/collections/describe.ts
similarity index 69%
rename from packages/sql-store/src/logic/collections.ts
rename to packages/sql-store/src/logic/collections/describe.ts
index 87abb6f..e01ff38 100644
--- a/packages/sql-store/src/logic/collections.ts
+++ b/packages/sql-store/src/logic/collections/describe.ts
@@ -2,48 +2,46 @@ import {
SQLColumn,
SQLIndexAttribute,
SQLIndexColumn,
- SQLTable,
- toStoreCollection_Slim,
toStoreField,
toStoreIndex,
} from '@/mappers';
-import { SQLConnection, dropTableIfExists } from '@/queries';
+import { SQLConnection } from '@/queries';
import {
StoreCollection,
- StoreCollection_Slim,
StoreDescribeCollectionOptions,
- StoreDropCollectionOptions,
StoreError,
StoreShapeType,
} from '@neuledge/store';
-export const getStoreCollections = async <
- A extends unknown[],
- T extends SQLTable,
->(
- listTables: (...args: A) => Promise,
- ...params: A
-): Promise => {
- const tables = await listTables(...params);
- return tables.map((table) => toStoreCollection_Slim(table));
-};
-
-export const getCollection = async <
- A extends unknown[],
+export interface DescribeCollectionQueries<
+ C extends SQLColumn,
+ I extends SQLIndexAttribute & Omit,
+> {
+ listTableColumns: (name: string, connection: SQLConnection) => Promise;
+ listIndexAttributes: (
+ name: string,
+ connection: SQLConnection,
+ ) => Promise;
+ dataTypeMap: Record;
+}
+
+export const describeCollection = async <
C extends SQLColumn,
I extends SQLIndexAttribute & Omit,
>(
options: StoreDescribeCollectionOptions,
- listTableColumns: (name: string, ...args: A) => Promise,
- listIndexAttributes: (name: string, ...args: A) => Promise,
- dataTypeMap: Record,
- ...params: A
+ connection: SQLConnection,
+ {
+ listTableColumns,
+ listIndexAttributes,
+ dataTypeMap,
+ }: DescribeCollectionQueries,
): Promise => {
const { name } = options.collection;
const [columns, indexAttributes] = await Promise.all([
- listTableColumns(name, ...params),
- listIndexAttributes(name, ...params),
+ listTableColumns(name, connection),
+ listIndexAttributes(name, connection),
]);
const columnMap = Object.fromEntries(
@@ -115,10 +113,3 @@ const groupIndexColumns = <
return Object.values(groupMap);
};
-
-export const dropCollection = async (
- options: StoreDropCollectionOptions,
- connection: SQLConnection,
-): Promise => {
- await dropTableIfExists(connection, options.collection.name);
-};
diff --git a/packages/sql-store/src/logic/collections/drop.ts b/packages/sql-store/src/logic/collections/drop.ts
new file mode 100644
index 0000000..f7e9d1b
--- /dev/null
+++ b/packages/sql-store/src/logic/collections/drop.ts
@@ -0,0 +1,9 @@
+import { SQLConnection, dropTableIfExists } from '@/queries';
+import { StoreDropCollectionOptions } from '@neuledge/store';
+
+export const dropCollection = async (
+ options: StoreDropCollectionOptions,
+ connection: SQLConnection,
+): Promise => {
+ await dropTableIfExists(connection, options.collection.name);
+};
diff --git a/packages/sql-store/src/logic/collections/ensure.ts b/packages/sql-store/src/logic/collections/ensure.ts
new file mode 100644
index 0000000..42ff986
--- /dev/null
+++ b/packages/sql-store/src/logic/collections/ensure.ts
@@ -0,0 +1,98 @@
+import {
+ SQLConnection,
+ dropColumn as dropColumnDefault,
+ dropIndex as dropIndexDefault,
+} from '@/queries';
+import pLimit from 'p-limit';
+import {
+ StoreCollection,
+ StoreEnsureCollectionOptions,
+ StoreField,
+ StoreIndex,
+} from '@neuledge/store';
+import { SQLColumn, SQLIndexAttribute, SQLIndexColumn } from '@/mappers';
+import { DescribeCollectionQueries, describeCollection } from './describe';
+
+export interface EnsureCollectionQueries {
+ createTableIfNotExists: (
+ collection: StoreCollection,
+ connection: SQLConnection,
+ ) => Promise;
+ addIndex: (
+ tableName: string,
+ index: StoreIndex,
+ connection: SQLConnection,
+ ) => Promise;
+ addColumn: (
+ tableName: string,
+ field: StoreField,
+ connection: SQLConnection,
+ ) => Promise;
+ dropIndex?: (
+ tableName: string,
+ index: string,
+ connection: SQLConnection,
+ ) => Promise;
+ dropColumn?: (
+ tableName: string,
+ field: string,
+ connection: SQLConnection,
+ ) => Promise;
+}
+
+export const ensureCollection = async <
+ C extends SQLColumn,
+ I extends SQLIndexAttribute & Omit,
+>(
+ options: StoreEnsureCollectionOptions,
+ connection: SQLConnection,
+ {
+ createTableIfNotExists,
+ addIndex,
+ addColumn,
+ dropIndex = dropIndexDefault,
+ dropColumn = dropColumnDefault,
+ ...describeCollectionQueries
+ }: EnsureCollectionQueries & DescribeCollectionQueries,
+): Promise => {
+ await createTableIfNotExists(options.collection, connection);
+
+ const tableName = options.collection.name;
+ const asyncLimit = pLimit(4);
+
+ await Promise.all(
+ options.dropIndexes?.map((index) =>
+ asyncLimit(() => dropIndex(tableName, index, connection)),
+ ) || [],
+ );
+
+ await Promise.all(
+ options.dropFields?.map((field) =>
+ asyncLimit(() => dropColumn(tableName, field, connection)),
+ ) || [],
+ );
+
+ const collection = await describeCollection(
+ options,
+ connection,
+ describeCollectionQueries,
+ );
+
+ // although we support adding columns with non-nullables types, it will be
+ // rejected by the database and for a good reason. It's the responsibility of
+ // the engine to ensure that new columns are nullable if inserted after the
+ // collection has been created and this is the current implementation.
+
+ await Promise.all([
+ ...(options.indexes
+ ?.filter((index) => !collection.indexes[index.name])
+ .map((index) =>
+ asyncLimit(() => addIndex(tableName, index, connection)),
+ ) || []),
+ ...(options.fields
+ ?.filter((field) => !collection.fields[field.name])
+ .map((field) =>
+ asyncLimit(() => addColumn(tableName, field, connection)),
+ ) || []),
+ ]);
+};
diff --git a/packages/sql-store/src/logic/collections/index.ts b/packages/sql-store/src/logic/collections/index.ts
new file mode 100644
index 0000000..cef6614
--- /dev/null
+++ b/packages/sql-store/src/logic/collections/index.ts
@@ -0,0 +1,4 @@
+export * from './describe';
+export * from './drop';
+export * from './ensure';
+export * from './list';
diff --git a/packages/sql-store/src/logic/collections/list.ts b/packages/sql-store/src/logic/collections/list.ts
new file mode 100644
index 0000000..bcddebb
--- /dev/null
+++ b/packages/sql-store/src/logic/collections/list.ts
@@ -0,0 +1,15 @@
+import { SQLTable, toStoreCollection_Slim } from '@/mappers';
+import { SQLConnection } from '@/queries';
+import { StoreCollection_Slim } from '@neuledge/store';
+
+export interface ListCollectionsQueries {
+ listTables: (connection: SQLConnection) => Promise;
+}
+
+export const listCollections = async (
+ connection: SQLConnection,
+ { listTables }: ListCollectionsQueries,
+): Promise => {
+ const tables = await listTables(connection);
+ return tables.map((table) => toStoreCollection_Slim(table));
+};
diff --git a/packages/sql-store/src/queries/drop-column.ts b/packages/sql-store/src/queries/drop-column.ts
new file mode 100644
index 0000000..6d775a8
--- /dev/null
+++ b/packages/sql-store/src/queries/drop-column.ts
@@ -0,0 +1,9 @@
+import { SQLConnection } from './connection';
+
+export const dropColumn = async (
+ tableName: string,
+ field: string,
+ connection: SQLConnection,
+): Promise => {
+ await connection.query(`ALTER TABLE ? DROP COLUMN ?`, [tableName, field]);
+};
diff --git a/packages/sql-store/src/queries/drop-index.ts b/packages/sql-store/src/queries/drop-index.ts
new file mode 100644
index 0000000..642fb80
--- /dev/null
+++ b/packages/sql-store/src/queries/drop-index.ts
@@ -0,0 +1,9 @@
+import { SQLConnection } from './connection';
+
+export const dropIndex = async (
+ tableName: string,
+ index: string,
+ connection: SQLConnection,
+): Promise => {
+ await connection.query(`DROP INDEX ? ON ?`, [index, tableName]);
+};
diff --git a/packages/sql-store/src/queries/index-columns.ts b/packages/sql-store/src/queries/index-columns.ts
new file mode 100644
index 0000000..9273dbe
--- /dev/null
+++ b/packages/sql-store/src/queries/index-columns.ts
@@ -0,0 +1,6 @@
+import { StoreIndex } from '@neuledge/store';
+
+export const indexColumns = (index: StoreIndex): string =>
+ Object.entries(index.fields)
+ .map(([key, val]) => `${key} ${val.sort === 'desc' ? 'DESC' : 'ASC'}`)
+ .join(', ');
diff --git a/packages/sql-store/src/queries/index.ts b/packages/sql-store/src/queries/index.ts
index b2a91d3..85a79c9 100644
--- a/packages/sql-store/src/queries/index.ts
+++ b/packages/sql-store/src/queries/index.ts
@@ -1,2 +1,5 @@
export * from './connection';
+export * from './drop-column';
+export * from './drop-index';
export * from './drop-table';
+export * from './index-columns';
diff --git a/yarn.lock b/yarn.lock
index 4e4479c..c300ed2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7520,47 +7520,47 @@ tunnel@0.0.6:
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
-turbo-darwin-64@1.8.8:
- version "1.8.8"
- resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.8.8.tgz#f72b1b6275415b17238f450032c8ef5e5fc71777"
- integrity sha512-18cSeIm7aeEvIxGyq7PVoFyEnPpWDM/0CpZvXKHpQ6qMTkfNt517qVqUTAwsIYqNS8xazcKAqkNbvU1V49n65Q==
-
-turbo-darwin-arm64@1.8.8:
- version "1.8.8"
- resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.8.8.tgz#8ec78848e0d5978fd732b3588a1b406fdb978839"
- integrity sha512-ruGRI9nHxojIGLQv1TPgN7ud4HO4V8mFBwSgO6oDoZTNuk5ybWybItGR+yu6fni5vJoyMHXOYA2srnxvOc7hjQ==
-
-turbo-linux-64@1.8.8:
- version "1.8.8"
- resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.8.8.tgz#b1f707b23bc6e22b2894dd8063fc2fa4dbb6ffb9"
- integrity sha512-N/GkHTHeIQogXB1/6ZWfxHx+ubYeb8Jlq3b/3jnU4zLucpZzTQ8XkXIAfJG/TL3Q7ON7xQ8yGOyGLhHL7MpFRg==
-
-turbo-linux-arm64@1.8.8:
- version "1.8.8"
- resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.8.8.tgz#34575bdffd2af8c835d9ba3dd9e3a83e0d31dac9"
- integrity sha512-hKqLbBHgUkYf2Ww8uBL9UYdBFQ5677a7QXdsFhONXoACbDUPvpK4BKlz3NN7G4NZ+g9dGju+OJJjQP0VXRHb5w==
-
-turbo-windows-64@1.8.8:
- version "1.8.8"
- resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.8.8.tgz#73f67969d54269c95cbf7f082e22c20368aedddc"
- integrity sha512-2ndjDJyzkNslXxLt+PQuU21AHJWc8f6MnLypXy3KsN4EyX/uKKGZS0QJWz27PeHg0JS75PVvhfFV+L9t9i+Yyg==
-
-turbo-windows-arm64@1.8.8:
- version "1.8.8"
- resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.8.8.tgz#c80b9a170adf6ee028e9dcae45b07755af83f3f2"
- integrity sha512-xCA3oxgmW9OMqpI34AAmKfOVsfDljhD5YBwgs0ZDsn5h3kCHhC4x9W5dDk1oyQ4F5EXSH3xVym5/xl1J6WRpUg==
-
-turbo@^1.8.8:
- version "1.8.8"
- resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.8.8.tgz#8bb331e3f0bd9656b20321339e91e899ad499012"
- integrity sha512-qYJ5NjoTX+591/x09KgsDOPVDUJfU9GoS+6jszQQlLp1AHrf1wRFA3Yps8U+/HTG03q0M4qouOfOLtRQP4QypA==
+turbo-darwin-64@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.9.1.tgz#1f04e716ad6cf071822f0c1a499f4fcd7bd40f4b"
+ integrity sha512-IX/Ph4CO80lFKd9pPx3BWpN2dynt6mcUFifyuHUNVkOP1Usza/G9YuZnKQFG6wUwKJbx40morFLjk1TTeLe04w==
+
+turbo-darwin-arm64@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.9.1.tgz#19ec161858fb26dfbd529e0ec9d6c9b4484e91b0"
+ integrity sha512-6tCbmIboy9dTbhIZ/x9KIpje73nvxbiyVnHbr9xKnsxLJavD0xqjHZzbL5U2tHp8chqmYf0E4WYOXd+XCNg+OQ==
+
+turbo-linux-64@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.9.1.tgz#5b47e0f0912f709a9a2e325feaa7e260aa76cf49"
+ integrity sha512-ti8XofnJFO1XaadL92lYJXgxb0VBl03Yu9VfhxkOTywFe7USTLBkJcdvQ4EpFk/KZwLiTdCmT2NQVxsG4AxBiQ==
+
+turbo-linux-arm64@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.9.1.tgz#db035061760e8512a408a64cc2d7d568f5102ab7"
+ integrity sha512-XYvIbeiCCCr+ENujd2Jtck/lJPTKWb8T2MSL/AEBx21Zy3Sa7HgrQX6LX0a0pNHjaleHz00XXt1D0W5hLeP+tA==
+
+turbo-windows-64@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.9.1.tgz#56ad98e4701b4523f118397d98d64ebef5dab88f"
+ integrity sha512-x7lWAspe4/v3XQ0gaFRWDX/X9uyWdhwFBPEfb8BA0YKtnsrPOHkV0mRHCRrXzvzjA7pcDCl2agGzb7o863O+Jg==
+
+turbo-windows-arm64@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.9.1.tgz#f0c780cc906dedff85eef20f063f59a9b2a865fc"
+ integrity sha512-QSLNz8dRBLDqXOUv/KnoesBomSbIz2Huef/a3l2+Pat5wkQVgMfzFxDOnkK5VWujPYXz+/prYz+/7cdaC78/kw==
+
+turbo@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.9.1.tgz#7ff6252cb7271142f82cff36cada918eaae67025"
+ integrity sha512-Rqe8SP96e53y4Pk29kk2aZbA8EF11UtHJ3vzXJseadrc1T3V6UhzvAWwiKJL//x/jojyOoX1axnoxmX3UHbZ0g==
optionalDependencies:
- turbo-darwin-64 "1.8.8"
- turbo-darwin-arm64 "1.8.8"
- turbo-linux-64 "1.8.8"
- turbo-linux-arm64 "1.8.8"
- turbo-windows-64 "1.8.8"
- turbo-windows-arm64 "1.8.8"
+ turbo-darwin-64 "1.9.1"
+ turbo-darwin-arm64 "1.9.1"
+ turbo-linux-64 "1.9.1"
+ turbo-linux-arm64 "1.9.1"
+ turbo-windows-64 "1.9.1"
+ turbo-windows-arm64 "1.9.1"
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
From db3563e64524aceae9067f2772a1e81b9c153734 Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Fri, 14 Apr 2023 18:33:03 +0300
Subject: [PATCH 08/24] test describeCollection on pg store
---
packages/mysql-store/src/store.ts | 14 +-
packages/postgresql-store/jest.config.json | 3 +
packages/postgresql-store/package.json | 1 +
.../src/queries/list-table-columns.ts | 76 +++---
.../src/queries/list-tables.ts | 4 +-
packages/postgresql-store/src/store.test.ts | 254 +++++++++++++++++-
packages/postgresql-store/src/store.ts | 22 +-
7 files changed, 315 insertions(+), 59 deletions(-)
create mode 100644 packages/postgresql-store/jest.config.json
diff --git a/packages/mysql-store/src/store.ts b/packages/mysql-store/src/store.ts
index 922fceb..522f486 100644
--- a/packages/mysql-store/src/store.ts
+++ b/packages/mysql-store/src/store.ts
@@ -1,4 +1,4 @@
-import { Pool, PoolConfig, createPool } from 'mysql';
+import { Connection, Pool, PoolConfig, createPool } from 'mysql';
import {
Store,
StoreCollection,
@@ -32,19 +32,21 @@ import {
listCollections,
} from '@neuledge/sql-store';
-export type MySQLStoreOptions = PoolConfig;
+export type MySQLStoreClient = Pool | Connection;
+
+export type MySQLStoreOptions = PoolConfig | { client: MySQLStoreClient };
export class MySQLStore implements Store {
- private pool: Pool;
+ private client: MySQLStoreClient;
private connection: SQLConnection;
constructor(options: MySQLStoreOptions) {
- this.pool = createPool(options);
+ this.client = 'client' in options ? options.client : createPool(options);
this.connection = {
query: (sql, values) =>
new Promise((resolve, reject) =>
- this.pool.query(sql, values, (error, results) =>
+ this.client.query(sql, values, (error, results) =>
error ? reject(error) : resolve(results),
),
),
@@ -55,7 +57,7 @@ export class MySQLStore implements Store {
async close(): Promise {
await new Promise((resolve, reject) =>
- this.pool.end((error) => (error ? reject(error) : resolve())),
+ this.client.end((error) => (error ? reject(error) : resolve())),
);
}
diff --git a/packages/postgresql-store/jest.config.json b/packages/postgresql-store/jest.config.json
new file mode 100644
index 0000000..5901941
--- /dev/null
+++ b/packages/postgresql-store/jest.config.json
@@ -0,0 +1,3 @@
+{
+ "preset": "@neuledge/jest-ts-preset"
+}
diff --git a/packages/postgresql-store/package.json b/packages/postgresql-store/package.json
index 9d64233..4d1091f 100644
--- a/packages/postgresql-store/package.json
+++ b/packages/postgresql-store/package.json
@@ -33,6 +33,7 @@
"scripts": {
"types": "rimraf --glob dist/*.{d.ts,d.ts.map} dist/**/*.{d.ts,d.ts.map} && tsc --emitDeclarationOnly && tsc-alias",
"build": "rimraf --glob dist/*.{js,js.map,mjs,mjs.map} && tsup",
+ "test": "jest",
"lint": "eslint . --ext \"js,jsx,ts,tsx,mjs,cjs\"",
"lint:strict": "yarn lint --max-warnings 0"
},
diff --git a/packages/postgresql-store/src/queries/list-table-columns.ts b/packages/postgresql-store/src/queries/list-table-columns.ts
index 7453fea..25948a1 100644
--- a/packages/postgresql-store/src/queries/list-table-columns.ts
+++ b/packages/postgresql-store/src/queries/list-table-columns.ts
@@ -25,53 +25,49 @@ export const listTableColumns = async (
[tableName],
);
+// https://www.postgresql.org/docs/current/datatype.html
export const dataTypeMap: Record = {
+ bigint: 'number',
+ bigserial: 'number',
+ bit: 'string',
+ 'bit varying': 'string',
+ boolean: 'boolean',
+ box: 'string',
+ bytea: 'string',
character: 'string',
'character varying': 'string',
+ cidr: 'string',
+ circle: 'string',
+ date: 'string',
'double precision': 'number',
- smallint: 'number',
- real: 'number',
- 'timestamp without time zone': 'date-time',
- 'timestamp with time zone': 'date-time',
- 'time without time zone': 'date-time',
- 'time with time zone': 'date-time',
- 'interval year to month': 'date-time',
- 'interval day to second': 'date-time',
- 'bit varying': 'binary',
- bit: 'binary',
- varbit: 'binary',
- bytea: 'binary',
- text: 'string',
+ inet: 'string',
+ integer: 'number',
+ interval: 'string',
json: 'json',
jsonb: 'json',
- uuid: 'string',
- xml: 'string',
- cidr: 'string',
- inet: 'string',
+ line: 'string',
+ lseg: 'string',
macaddr: 'string',
- tsvector: 'string',
- tsquery: 'string',
- regconfig: 'string',
- regdictionary: 'string',
- regnamespace: 'string',
- regoper: 'string',
- regoperator: 'string',
- regproc: 'string',
- regprocedure: 'string',
- regrole: 'string',
- regtype: 'string',
- int2: 'number',
- int4: 'number',
- int8: 'number',
- float4: 'number',
- float8: 'number',
- bool: 'boolean',
- date: 'date-time',
+ money: 'string',
+ numeric: 'number',
+ path: 'string',
+ pg_lsn: 'string',
+ point: 'string',
+ polygon: 'string',
+ real: 'number',
+ smallint: 'number',
+ smallserial: 'number',
+ serial: 'number',
+ text: 'string',
time: 'date-time',
+ 'time with time zone': 'date-time',
+ 'time without time zone': 'date-time',
timestamp: 'date-time',
- timestamptz: 'date-time',
- interval: 'date-time',
- numeric: 'number',
- decimal: 'number',
- money: 'number',
+ 'timestamp with time zone': 'date-time',
+ 'timestamp without time zone': 'date-time',
+ tsquery: 'string',
+ tsvector: 'string',
+ txid_snapshot: 'string',
+ uuid: 'string',
+ xml: 'string',
};
diff --git a/packages/postgresql-store/src/queries/list-tables.ts b/packages/postgresql-store/src/queries/list-tables.ts
index 933bc0e..a8eee46 100644
--- a/packages/postgresql-store/src/queries/list-tables.ts
+++ b/packages/postgresql-store/src/queries/list-tables.ts
@@ -11,5 +11,7 @@ export const listTables = async (
connection: SQLConnection,
): Promise =>
connection.query(
- `SELECT table_name FROM information_schema.tables WHERE table_catalog = current_database() AND table_schema = current_schema()`,
+ `SELECT table_name
+ FROM information_schema.tables
+ WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_type = 'BASE TABLE'`,
);
diff --git a/packages/postgresql-store/src/store.test.ts b/packages/postgresql-store/src/store.test.ts
index 944909c..39b22e3 100644
--- a/packages/postgresql-store/src/store.test.ts
+++ b/packages/postgresql-store/src/store.test.ts
@@ -1,11 +1,13 @@
import { PostgreSQLStore } from './store';
+/* eslint-disable max-lines-per-function */
+
describe('store', () => {
describe('PostgreSQLStore()', () => {
describe('.constructor()', () => {
it('should be able to create a new store', () => {
const store = new PostgreSQLStore({
- pool: {} as never,
+ client: {} as never,
});
expect(store).toBeInstanceOf(PostgreSQLStore);
@@ -17,7 +19,7 @@ describe('store', () => {
const end = jest.fn();
const store = new PostgreSQLStore({
- pool: { end } as never,
+ client: { end } as never,
});
expect(store).toBeInstanceOf(PostgreSQLStore);
@@ -27,5 +29,253 @@ describe('store', () => {
expect(end).toHaveBeenCalledTimes(1);
});
});
+
+ let store: PostgreSQLStore;
+ let query: jest.Mock;
+
+ beforeEach(() => {
+ query = jest.fn();
+
+ store = new PostgreSQLStore({
+ client: { query } as never,
+ });
+ });
+
+ describe('.listCollections()', () => {
+ it('should be able to list collections', async () => {
+ query.mockResolvedValueOnce({
+ rows: [{ table_name: 'foo' }, { table_name: 'bar' }],
+ });
+
+ const collections = await store.listCollections();
+
+ expect(query).toHaveBeenCalledTimes(1);
+ expect(query).toHaveBeenCalledWith(
+ `SELECT table_name
+ FROM information_schema.tables
+ WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_type = 'BASE TABLE'`,
+ undefined,
+ );
+
+ expect(collections).toEqual([{ name: 'foo' }, { name: 'bar' }]);
+ });
+ });
+
+ describe('.describeCollection()', () => {
+ it('should be able to describe a collection', async () => {
+ query.mockResolvedValueOnce({
+ rows: [
+ {
+ column_name: 'id',
+ data_type: 'integer',
+ character_maximum_length: null,
+ numeric_precision: 32,
+ numeric_scale: 0,
+ is_nullable: false,
+ is_auto_increment: true,
+ },
+ {
+ column_name: 'name',
+ data_type: 'character varying',
+ character_maximum_length: 50,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: true,
+ is_auto_increment: null,
+ },
+ {
+ column_name: 'email',
+ data_type: 'character varying',
+ character_maximum_length: 100,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: false,
+ is_auto_increment: null,
+ },
+ {
+ column_name: 'phone',
+ data_type: 'character varying',
+ character_maximum_length: 20,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: true,
+ is_auto_increment: null,
+ },
+ {
+ column_name: 'created_at',
+ data_type: 'timestamp without time zone',
+ character_maximum_length: null,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: false,
+ is_auto_increment: null,
+ },
+ {
+ column_name: 'updated_at',
+ data_type: 'timestamp without time zone',
+ character_maximum_length: null,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: false,
+ is_auto_increment: null,
+ },
+ ],
+ });
+
+ query.mockResolvedValueOnce({
+ rows: [
+ {
+ index_name: 'idx_email',
+ column_name: 'email',
+ seq_in_index: 1,
+ direction: 'ASC',
+ nulls: 'LAST',
+ is_unique: true,
+ is_primary: false,
+ },
+ {
+ index_name: 'idx_phone_email',
+ column_name: 'phone',
+ seq_in_index: 1,
+ direction: 'DESC',
+ nulls: 'FIRST',
+ is_unique: false,
+ is_primary: false,
+ },
+ {
+ index_name: 'idx_phone_email',
+ column_name: 'email',
+ seq_in_index: 2,
+ direction: 'ASC',
+ nulls: 'LAST',
+ is_unique: false,
+ is_primary: false,
+ },
+ {
+ index_name: 'foo_pkey',
+ column_name: 'id',
+ seq_in_index: 1,
+ direction: 'ASC',
+ nulls: 'LAST',
+ is_unique: true,
+ is_primary: true,
+ },
+ ],
+ });
+
+ const collection = await store.describeCollection({
+ collection: { name: 'foo' },
+ });
+
+ expect(query).toHaveBeenCalledTimes(2);
+ expect(query).toHaveBeenNthCalledWith(
+ 1,
+ `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, (is_nullable = 'YES') as is_nullable, column_default LIKE 'nextval(%)' AS is_auto_increment
+ FROM information_schema.columns
+ WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_name = ?`,
+ ['foo'],
+ );
+ expect(query).toHaveBeenNthCalledWith(
+ 2,
+ `SELECT
+ irel.relname AS index_name,
+ a.attname AS column_name,
+ c.ordinality as seq_in_index,
+ CASE o.option & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction,
+ CASE o.option & 2 WHEN 2 THEN 'FIRST' ELSE 'LAST' END AS nulls,
+ i.indisunique AS is_unique,
+ i.indisprimary AS is_primary
+ FROM pg_index AS i
+ JOIN pg_class AS trel ON trel.oid = i.indrelid
+ JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
+ JOIN pg_class AS irel ON irel.oid = i.indexrelid
+ CROSS JOIN LATERAL unnest (i.indkey) WITH ORDINALITY AS c (colnum, ordinality)
+ LEFT JOIN LATERAL unnest (i.indoption) WITH ORDINALITY AS o (option, ordinality)
+ ON c.ordinality = o.ordinality
+ JOIN pg_attribute AS a ON trel.oid = a.attrelid AND a.attnum = c.colnum
+ WHERE tnsp.nspname = current_schema() AND trel.relname = ?
+ ORDER BY index_name, seq_in_index`,
+ ['foo'],
+ );
+
+ expect(collection).toEqual({
+ name: 'foo',
+ fields: {
+ id: {
+ name: 'id',
+ type: 'number',
+ nullable: false,
+ size: null,
+ precision: 32,
+ scale: 0,
+ },
+ name: {
+ name: 'name',
+ type: 'string',
+ nullable: true,
+ size: 50,
+ precision: null,
+ scale: null,
+ },
+ email: {
+ name: 'email',
+ type: 'string',
+ nullable: false,
+ size: 100,
+ precision: null,
+ scale: null,
+ },
+ phone: {
+ name: 'phone',
+ type: 'string',
+ nullable: true,
+ size: 20,
+ precision: null,
+ scale: null,
+ },
+ created_at: {
+ name: 'created_at',
+ type: 'date-time',
+ nullable: false,
+ size: null,
+ precision: null,
+ scale: null,
+ },
+ updated_at: {
+ name: 'updated_at',
+ type: 'date-time',
+ nullable: false,
+ size: null,
+ precision: null,
+ scale: null,
+ },
+ },
+ primaryKey: {
+ name: 'foo_pkey',
+ fields: { id: { sort: 'asc' } },
+ unique: 'primary',
+ auto: 'increment',
+ },
+ indexes: {
+ foo_pkey: {
+ name: 'foo_pkey',
+ fields: { id: { sort: 'asc' } },
+ unique: 'primary',
+ auto: 'increment',
+ },
+ idx_email: {
+ name: 'idx_email',
+ fields: { email: { sort: 'asc' } },
+ unique: true,
+ },
+ idx_phone_email: {
+ name: 'idx_phone_email',
+ fields: { phone: { sort: 'desc' }, email: { sort: 'asc' } },
+ unique: false,
+ },
+ },
+ });
+ });
+ });
});
});
diff --git a/packages/postgresql-store/src/store.ts b/packages/postgresql-store/src/store.ts
index b917ada..26ac942 100644
--- a/packages/postgresql-store/src/store.ts
+++ b/packages/postgresql-store/src/store.ts
@@ -1,4 +1,4 @@
-import { Pool, PoolConfig } from 'pg';
+import { Client, Pool, PoolConfig } from 'pg';
import {
dataTypeMap,
listTableColumns,
@@ -26,36 +26,38 @@ import {
StoreUpdateOptions,
} from '@neuledge/store';
import {
- SQLConnection,
dropCollection,
describeCollection,
listCollections,
ensureCollection,
+ SQLConnection,
} from '@neuledge/sql-store';
-export type PostgreSQLStorePool =
- | (SQLConnection & { end: () => unknown })
- | Pool;
+export type PostgreSQLStoreClient = Client | Pool;
export type PostgreSQLStoreOptions =
| PoolConfig
| {
- pool: PostgreSQLStorePool;
+ client: PostgreSQLStoreClient;
};
export class PostgreSQLStore implements Store {
- private pool: PostgreSQLStorePool;
+ private client: PostgreSQLStoreClient;
private connection: SQLConnection;
constructor(options: PostgreSQLStoreOptions) {
- this.pool = 'pool' in options ? options.pool : new Pool(options);
- this.connection = this.pool;
+ this.client = 'client' in options ? options.client : new Pool(options);
+
+ this.connection = {
+ query: (sql, values) =>
+ this.client.query(sql, values).then((result) => result.rows as never),
+ };
}
// connection methods
async close(): Promise {
- await this.pool.end();
+ await this.client.end();
}
// store methods
From 321de110c3abe31cc78112b8e5db4088d22c6537 Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Sat, 15 Apr 2023 00:28:36 +0300
Subject: [PATCH 09/24] test pg createTableIfNotExists
---
.../src/queries/__fixtures__/users-table.ts | 196 +++++++++++++
.../src/queries/add-column.ts | 88 +++++-
.../src/queries/create-table.ts | 19 +-
.../src/queries/list-table-columns.ts | 13 +-
.../src/queries/list-table-statistics.ts | 43 +--
.../src/queries/list-tables.ts | 10 +-
packages/postgresql-store/src/store.test.ts | 264 ++++--------------
packages/postgresql-store/src/store.ts | 4 +-
packages/store/src/collection.ts | 2 +-
9 files changed, 382 insertions(+), 257 deletions(-)
create mode 100644 packages/postgresql-store/src/queries/__fixtures__/users-table.ts
diff --git a/packages/postgresql-store/src/queries/__fixtures__/users-table.ts b/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
new file mode 100644
index 0000000..baf5ff4
--- /dev/null
+++ b/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
@@ -0,0 +1,196 @@
+import { StoreCollection, StoreCollection_Slim } from '@neuledge/store';
+import { PostgreSQLTable } from '../list-tables';
+import { PostgreSQLColumn } from '../list-table-columns';
+import { PostgreSQLIndexAttribute } from '..';
+
+export const usersTableName = 'users';
+
+export const usersTable_createSql = `CREATE TABLE IF NOT EXISTS ${usersTableName} (
+ id BIGSERIAL NOT NULL,
+ name VARCHAR(50),
+ email VARCHAR(100) NOT NULL,
+ phone VARCHAR(20),
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL,
+ CONSTRAINT ${usersTableName}_pkey PRIMARY KEY (id)
+)`;
+
+export const usersTable: PostgreSQLTable = { table_name: usersTableName };
+
+export const usersCollection_slim: StoreCollection_Slim = {
+ name: usersTableName,
+};
+
+export const usersTableColumns: PostgreSQLColumn[] = [
+ {
+ column_name: 'id',
+ data_type: 'integer',
+ character_maximum_length: null,
+ numeric_precision: 32,
+ numeric_scale: 0,
+ is_nullable: false,
+ is_auto_increment: true,
+ },
+ {
+ column_name: 'name',
+ data_type: 'character varying',
+ character_maximum_length: 50,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: true,
+ is_auto_increment: null,
+ },
+ {
+ column_name: 'email',
+ data_type: 'character varying',
+ character_maximum_length: 100,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: false,
+ is_auto_increment: null,
+ },
+ {
+ column_name: 'phone',
+ data_type: 'character varying',
+ character_maximum_length: 20,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: true,
+ is_auto_increment: null,
+ },
+ {
+ column_name: 'created_at',
+ data_type: 'timestamp without time zone',
+ character_maximum_length: null,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: false,
+ is_auto_increment: null,
+ },
+ {
+ column_name: 'updated_at',
+ data_type: 'timestamp without time zone',
+ character_maximum_length: null,
+ numeric_precision: null,
+ numeric_scale: null,
+ is_nullable: false,
+ is_auto_increment: null,
+ },
+];
+
+export const usersTableIndexes: PostgreSQLIndexAttribute[] = [
+ {
+ index_name: 'idx_email',
+ column_name: 'email',
+ seq_in_index: 1,
+ direction: 'ASC',
+ nulls: 'LAST',
+ is_unique: true,
+ is_primary: false,
+ },
+ {
+ index_name: 'idx_phone_email',
+ column_name: 'phone',
+ seq_in_index: 1,
+ direction: 'DESC',
+ nulls: 'FIRST',
+ is_unique: false,
+ is_primary: false,
+ },
+ {
+ index_name: 'idx_phone_email',
+ column_name: 'email',
+ seq_in_index: 2,
+ direction: 'ASC',
+ nulls: 'LAST',
+ is_unique: false,
+ is_primary: false,
+ },
+ {
+ index_name: 'users_pkey',
+ column_name: 'id',
+ seq_in_index: 1,
+ direction: 'ASC',
+ nulls: 'LAST',
+ is_unique: true,
+ is_primary: true,
+ },
+];
+
+export const usersCollection: StoreCollection = {
+ name: usersTableName,
+ fields: {
+ id: {
+ name: 'id',
+ type: 'number',
+ nullable: false,
+ size: null,
+ precision: 32,
+ scale: 0,
+ },
+ name: {
+ name: 'name',
+ type: 'string',
+ nullable: true,
+ size: 50,
+ precision: null,
+ scale: null,
+ },
+ email: {
+ name: 'email',
+ type: 'string',
+ nullable: false,
+ size: 100,
+ precision: null,
+ scale: null,
+ },
+ phone: {
+ name: 'phone',
+ type: 'string',
+ nullable: true,
+ size: 20,
+ precision: null,
+ scale: null,
+ },
+ created_at: {
+ name: 'created_at',
+ type: 'date-time',
+ nullable: false,
+ size: null,
+ precision: null,
+ scale: null,
+ },
+ updated_at: {
+ name: 'updated_at',
+ type: 'date-time',
+ nullable: false,
+ size: null,
+ precision: null,
+ scale: null,
+ },
+ },
+ primaryKey: {
+ name: `${usersTableName}_pkey`,
+ fields: { id: { sort: 'asc' } },
+ unique: 'primary',
+ auto: 'increment',
+ },
+ indexes: {
+ [`${usersTableName}_pkey`]: {
+ name: `${usersTableName}_pkey`,
+ fields: { id: { sort: 'asc' } },
+ unique: 'primary',
+ auto: 'increment',
+ },
+ idx_email: {
+ name: 'idx_email',
+ fields: { email: { sort: 'asc' } },
+ unique: true,
+ },
+ idx_phone_email: {
+ name: 'idx_phone_email',
+ fields: { phone: { sort: 'desc' }, email: { sort: 'asc' } },
+ unique: false,
+ },
+ },
+};
diff --git a/packages/postgresql-store/src/queries/add-column.ts b/packages/postgresql-store/src/queries/add-column.ts
index 0526579..6b1800b 100644
--- a/packages/postgresql-store/src/queries/add-column.ts
+++ b/packages/postgresql-store/src/queries/add-column.ts
@@ -1,8 +1,94 @@
import { SQLConnection } from '@neuledge/sql-store';
-import { StoreField } from '@neuledge/store';
+import { StoreCollection, StoreField } from '@neuledge/store';
export const addColumn = async (
tableName: string,
field: StoreField,
connection: SQLConnection,
): Promise => {};
+
+export const getColumnDefinition = (
+ field: StoreField,
+ collection: StoreCollection,
+): string =>
+ `${field.name} ${getColumnDataType(field, collection)}${
+ field.nullable ? '' : ' NOT NULL'
+ }`;
+
+const getColumnDataType = (
+ field: StoreField,
+ collection: StoreCollection,
+): string => {
+ switch (field.type) {
+ case 'string': {
+ if (field.size) {
+ return `VARCHAR(${field.size})`;
+ }
+
+ return 'TEXT';
+ }
+
+ case 'number': {
+ return getNumberDateType(field, collection);
+ }
+
+ case 'date-time': {
+ return 'TIMESTAMP';
+ }
+
+ case 'boolean': {
+ return 'BOOLEAN';
+ }
+
+ case 'json': {
+ return 'JSONB';
+ }
+
+ case 'enum': {
+ return `ENUM(${field.values?.map((value) => `'${value}'`).join(', ')})`;
+ }
+
+ case 'binary': {
+ return 'BYTEA';
+ }
+
+ default: {
+ throw new Error(`Unsupported field type: ${field.type}`);
+ }
+ }
+};
+
+// https://www.postgresql.org/docs/current/datatype-numeric.html
+const getNumberDateType = (
+ field: StoreField,
+ collection: StoreCollection,
+): string => {
+ if (field.scale === 0) {
+ if (
+ collection.primaryKey.auto === 'increment' &&
+ collection.primaryKey.fields[field.name]
+ ) {
+ if (!field.precision || field.precision >= 10) {
+ return 'BIGSERIAL';
+ }
+
+ if (field.precision < 5) {
+ return 'SMALLSERIAL';
+ }
+
+ return 'SERIAL';
+ }
+
+ if (field.precision) {
+ return `NUMERIC(${field.precision})`;
+ }
+
+ return 'BIGINT';
+ }
+
+ if (field.precision && field.scale) {
+ return `NUMERIC(${field.precision}, ${field.scale})`;
+ }
+
+ return 'DOUBLE PRECISION';
+};
diff --git a/packages/postgresql-store/src/queries/create-table.ts b/packages/postgresql-store/src/queries/create-table.ts
index bb63a12..6a0a9e1 100644
--- a/packages/postgresql-store/src/queries/create-table.ts
+++ b/packages/postgresql-store/src/queries/create-table.ts
@@ -1,15 +1,22 @@
-import { SQLConnection, indexColumns } from '@neuledge/sql-store';
+import { SQLConnection } from '@neuledge/sql-store';
import { StoreCollection } from '@neuledge/store';
+import { getColumnDefinition } from './add-column';
export const createTableIfNotExists = async (
collection: StoreCollection,
connection: SQLConnection,
): Promise => {
await connection.query(
- `CREATE TABLE IF NOT EXISTS ? (
- ${/* FIXME add columns */ ''}
- CONSTRAINT ? PRIMARY KEY (${indexColumns(collection.primaryKey)})
- )`,
- [collection.name],
+ `CREATE TABLE IF NOT EXISTS ${collection.name} (
+ ${Object.values(collection.fields)
+ .map((field) => getColumnDefinition(field, collection))
+ .join(',\n ')},
+ CONSTRAINT ${collection.name}_pkey PRIMARY KEY (${Object.keys(
+ collection.primaryKey.fields,
+ ).join(', ')})
+)`,
);
+
+ // FIXME add unique constraints if primary key has descending fields
+ // https://stackoverflow.com/a/45604459/518153
};
diff --git a/packages/postgresql-store/src/queries/list-table-columns.ts b/packages/postgresql-store/src/queries/list-table-columns.ts
index 25948a1..029775b 100644
--- a/packages/postgresql-store/src/queries/list-table-columns.ts
+++ b/packages/postgresql-store/src/queries/list-table-columns.ts
@@ -11,19 +11,18 @@ export interface PostgreSQLColumn {
numeric_precision: number | null;
numeric_scale: number | null;
is_nullable: boolean;
- is_auto_increment: boolean;
+ is_auto_increment: boolean | null;
}
export const listTableColumns = async (
tableName: string,
connection: SQLConnection,
): Promise =>
- connection.query(
- `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, (is_nullable = 'YES') as is_nullable, column_default LIKE 'nextval(%)' AS is_auto_increment
- FROM information_schema.columns
- WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_name = ?`,
- [tableName],
- );
+ connection.query(listTableColumns_sql, [tableName]);
+
+export const listTableColumns_sql = `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, (is_nullable = 'YES') as is_nullable, column_default LIKE 'nextval(%)' AS is_auto_increment
+FROM information_schema.columns
+WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_name = ?`;
// https://www.postgresql.org/docs/current/datatype.html
export const dataTypeMap: Record = {
diff --git a/packages/postgresql-store/src/queries/list-table-statistics.ts b/packages/postgresql-store/src/queries/list-table-statistics.ts
index 325f8b3..e6a2006 100644
--- a/packages/postgresql-store/src/queries/list-table-statistics.ts
+++ b/packages/postgresql-store/src/queries/list-table-statistics.ts
@@ -17,24 +17,25 @@ export const listIndexAttributes = async (
tableName: string,
connection: SQLConnection,
): Promise =>
- connection.query(
- `SELECT
- irel.relname AS index_name,
- a.attname AS column_name,
- c.ordinality as seq_in_index,
- CASE o.option & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction,
- CASE o.option & 2 WHEN 2 THEN 'FIRST' ELSE 'LAST' END AS nulls,
- i.indisunique AS is_unique,
- i.indisprimary AS is_primary
- FROM pg_index AS i
- JOIN pg_class AS trel ON trel.oid = i.indrelid
- JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
- JOIN pg_class AS irel ON irel.oid = i.indexrelid
- CROSS JOIN LATERAL unnest (i.indkey) WITH ORDINALITY AS c (colnum, ordinality)
- LEFT JOIN LATERAL unnest (i.indoption) WITH ORDINALITY AS o (option, ordinality)
- ON c.ordinality = o.ordinality
- JOIN pg_attribute AS a ON trel.oid = a.attrelid AND a.attnum = c.colnum
- WHERE tnsp.nspname = current_schema() AND trel.relname = ?
- ORDER BY index_name, seq_in_index`,
- [tableName],
- );
+ connection.query(listIndexAttributes_sql, [
+ tableName,
+ ]);
+
+export const listIndexAttributes_sql = `SELECT
+irel.relname AS index_name,
+a.attname AS column_name,
+c.ordinality as seq_in_index,
+CASE o.option & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction,
+CASE o.option & 2 WHEN 2 THEN 'FIRST' ELSE 'LAST' END AS nulls,
+i.indisunique AS is_unique,
+i.indisprimary AS is_primary
+FROM pg_index AS i
+JOIN pg_class AS trel ON trel.oid = i.indrelid
+JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
+JOIN pg_class AS irel ON irel.oid = i.indexrelid
+CROSS JOIN LATERAL unnest (i.indkey) WITH ORDINALITY AS c (colnum, ordinality)
+LEFT JOIN LATERAL unnest (i.indoption) WITH ORDINALITY AS o (option, ordinality)
+ON c.ordinality = o.ordinality
+JOIN pg_attribute AS a ON trel.oid = a.attrelid AND a.attnum = c.colnum
+WHERE tnsp.nspname = current_schema() AND trel.relname = ?
+ORDER BY index_name, seq_in_index`;
diff --git a/packages/postgresql-store/src/queries/list-tables.ts b/packages/postgresql-store/src/queries/list-tables.ts
index a8eee46..e4aad48 100644
--- a/packages/postgresql-store/src/queries/list-tables.ts
+++ b/packages/postgresql-store/src/queries/list-tables.ts
@@ -10,8 +10,8 @@ export interface PostgreSQLTable {
export const listTables = async (
connection: SQLConnection,
): Promise =>
- connection.query(
- `SELECT table_name
- FROM information_schema.tables
- WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_type = 'BASE TABLE'`,
- );
+ connection.query(listTables_sql);
+
+export const listTables_sql = `SELECT table_name
+FROM information_schema.tables
+WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_type = 'BASE TABLE'`;
diff --git a/packages/postgresql-store/src/store.test.ts b/packages/postgresql-store/src/store.test.ts
index 39b22e3..95d2b85 100644
--- a/packages/postgresql-store/src/store.test.ts
+++ b/packages/postgresql-store/src/store.test.ts
@@ -1,3 +1,17 @@
+import {
+ listIndexAttributes_sql,
+ listTableColumns_sql,
+ listTables_sql,
+} from './queries';
+import {
+ usersCollection,
+ usersCollection_slim,
+ usersTable,
+ usersTableColumns,
+ usersTableIndexes,
+ usersTableName,
+ usersTable_createSql,
+} from './queries/__fixtures__/users-table';
import { PostgreSQLStore } from './store';
/* eslint-disable max-lines-per-function */
@@ -43,238 +57,58 @@ describe('store', () => {
describe('.listCollections()', () => {
it('should be able to list collections', async () => {
- query.mockResolvedValueOnce({
- rows: [{ table_name: 'foo' }, { table_name: 'bar' }],
- });
+ query.mockResolvedValueOnce({ rows: [usersTable] });
const collections = await store.listCollections();
expect(query).toHaveBeenCalledTimes(1);
- expect(query).toHaveBeenCalledWith(
- `SELECT table_name
- FROM information_schema.tables
- WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_type = 'BASE TABLE'`,
- undefined,
- );
+ expect(query).toHaveBeenCalledWith(listTables_sql, []);
- expect(collections).toEqual([{ name: 'foo' }, { name: 'bar' }]);
+ expect(collections).toEqual([usersCollection_slim]);
});
});
describe('.describeCollection()', () => {
it('should be able to describe a collection', async () => {
- query.mockResolvedValueOnce({
- rows: [
- {
- column_name: 'id',
- data_type: 'integer',
- character_maximum_length: null,
- numeric_precision: 32,
- numeric_scale: 0,
- is_nullable: false,
- is_auto_increment: true,
- },
- {
- column_name: 'name',
- data_type: 'character varying',
- character_maximum_length: 50,
- numeric_precision: null,
- numeric_scale: null,
- is_nullable: true,
- is_auto_increment: null,
- },
- {
- column_name: 'email',
- data_type: 'character varying',
- character_maximum_length: 100,
- numeric_precision: null,
- numeric_scale: null,
- is_nullable: false,
- is_auto_increment: null,
- },
- {
- column_name: 'phone',
- data_type: 'character varying',
- character_maximum_length: 20,
- numeric_precision: null,
- numeric_scale: null,
- is_nullable: true,
- is_auto_increment: null,
- },
- {
- column_name: 'created_at',
- data_type: 'timestamp without time zone',
- character_maximum_length: null,
- numeric_precision: null,
- numeric_scale: null,
- is_nullable: false,
- is_auto_increment: null,
- },
- {
- column_name: 'updated_at',
- data_type: 'timestamp without time zone',
- character_maximum_length: null,
- numeric_precision: null,
- numeric_scale: null,
- is_nullable: false,
- is_auto_increment: null,
- },
- ],
- });
-
- query.mockResolvedValueOnce({
- rows: [
- {
- index_name: 'idx_email',
- column_name: 'email',
- seq_in_index: 1,
- direction: 'ASC',
- nulls: 'LAST',
- is_unique: true,
- is_primary: false,
- },
- {
- index_name: 'idx_phone_email',
- column_name: 'phone',
- seq_in_index: 1,
- direction: 'DESC',
- nulls: 'FIRST',
- is_unique: false,
- is_primary: false,
- },
- {
- index_name: 'idx_phone_email',
- column_name: 'email',
- seq_in_index: 2,
- direction: 'ASC',
- nulls: 'LAST',
- is_unique: false,
- is_primary: false,
- },
- {
- index_name: 'foo_pkey',
- column_name: 'id',
- seq_in_index: 1,
- direction: 'ASC',
- nulls: 'LAST',
- is_unique: true,
- is_primary: true,
- },
- ],
- });
+ query.mockResolvedValueOnce({ rows: usersTableColumns });
+ query.mockResolvedValueOnce({ rows: usersTableIndexes });
const collection = await store.describeCollection({
- collection: { name: 'foo' },
+ collection: usersCollection_slim,
});
expect(query).toHaveBeenCalledTimes(2);
- expect(query).toHaveBeenNthCalledWith(
- 1,
- `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, (is_nullable = 'YES') as is_nullable, column_default LIKE 'nextval(%)' AS is_auto_increment
- FROM information_schema.columns
- WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_name = ?`,
- ['foo'],
- );
- expect(query).toHaveBeenNthCalledWith(
- 2,
- `SELECT
- irel.relname AS index_name,
- a.attname AS column_name,
- c.ordinality as seq_in_index,
- CASE o.option & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END AS direction,
- CASE o.option & 2 WHEN 2 THEN 'FIRST' ELSE 'LAST' END AS nulls,
- i.indisunique AS is_unique,
- i.indisprimary AS is_primary
- FROM pg_index AS i
- JOIN pg_class AS trel ON trel.oid = i.indrelid
- JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
- JOIN pg_class AS irel ON irel.oid = i.indexrelid
- CROSS JOIN LATERAL unnest (i.indkey) WITH ORDINALITY AS c (colnum, ordinality)
- LEFT JOIN LATERAL unnest (i.indoption) WITH ORDINALITY AS o (option, ordinality)
- ON c.ordinality = o.ordinality
- JOIN pg_attribute AS a ON trel.oid = a.attrelid AND a.attnum = c.colnum
- WHERE tnsp.nspname = current_schema() AND trel.relname = ?
- ORDER BY index_name, seq_in_index`,
- ['foo'],
- );
+ expect(query).toHaveBeenNthCalledWith(1, listTableColumns_sql, [
+ usersTableName,
+ ]);
+ expect(query).toHaveBeenNthCalledWith(2, listIndexAttributes_sql, [
+ usersTableName,
+ ]);
+
+ expect(collection).toEqual(usersCollection);
+ });
+ });
- expect(collection).toEqual({
- name: 'foo',
- fields: {
- id: {
- name: 'id',
- type: 'number',
- nullable: false,
- size: null,
- precision: 32,
- scale: 0,
- },
- name: {
- name: 'name',
- type: 'string',
- nullable: true,
- size: 50,
- precision: null,
- scale: null,
- },
- email: {
- name: 'email',
- type: 'string',
- nullable: false,
- size: 100,
- precision: null,
- scale: null,
- },
- phone: {
- name: 'phone',
- type: 'string',
- nullable: true,
- size: 20,
- precision: null,
- scale: null,
- },
- created_at: {
- name: 'created_at',
- type: 'date-time',
- nullable: false,
- size: null,
- precision: null,
- scale: null,
- },
- updated_at: {
- name: 'updated_at',
- type: 'date-time',
- nullable: false,
- size: null,
- precision: null,
- scale: null,
- },
- },
- primaryKey: {
- name: 'foo_pkey',
- fields: { id: { sort: 'asc' } },
- unique: 'primary',
- auto: 'increment',
- },
- indexes: {
- foo_pkey: {
- name: 'foo_pkey',
- fields: { id: { sort: 'asc' } },
- unique: 'primary',
- auto: 'increment',
- },
- idx_email: {
- name: 'idx_email',
- fields: { email: { sort: 'asc' } },
- unique: true,
- },
- idx_phone_email: {
- name: 'idx_phone_email',
- fields: { phone: { sort: 'desc' }, email: { sort: 'asc' } },
- unique: false,
- },
- },
+ describe('.ensureCollection()', () => {
+ it('should skip create an existing table', async () => {
+ query.mockResolvedValueOnce({ rows: [] });
+ query.mockResolvedValueOnce({ rows: usersTableColumns });
+ query.mockResolvedValueOnce({ rows: usersTableIndexes });
+
+ await store.ensureCollection({
+ collection: usersCollection,
+ fields: Object.values(usersCollection.fields),
+ indexes: Object.values(usersCollection.indexes),
});
+
+ expect(query).toHaveBeenCalledTimes(3);
+ expect(query).toHaveBeenNthCalledWith(1, usersTable_createSql, []);
+ expect(query).toHaveBeenNthCalledWith(2, listTableColumns_sql, [
+ usersTableName,
+ ]);
+ expect(query).toHaveBeenNthCalledWith(3, listIndexAttributes_sql, [
+ usersTableName,
+ ]);
});
});
});
diff --git a/packages/postgresql-store/src/store.ts b/packages/postgresql-store/src/store.ts
index 26ac942..9956eb2 100644
--- a/packages/postgresql-store/src/store.ts
+++ b/packages/postgresql-store/src/store.ts
@@ -50,7 +50,9 @@ export class PostgreSQLStore implements Store {
this.connection = {
query: (sql, values) =>
- this.client.query(sql, values).then((result) => result.rows as never),
+ this.client
+ .query(sql, values ?? [])
+ .then((result) => result.rows as never),
};
}
diff --git a/packages/store/src/collection.ts b/packages/store/src/collection.ts
index ea40065..3838d53 100644
--- a/packages/store/src/collection.ts
+++ b/packages/store/src/collection.ts
@@ -4,7 +4,7 @@ import { StoreSortDirection } from './sort';
export interface StoreCollection {
name: string;
primaryKey: StorePrimaryKey;
- indexes: Record;
+ indexes: Record;
fields: Record;
}
From 795dff4a2fd2057557221da6deadf9cafd5d11d1 Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Sat, 15 Apr 2023 01:09:35 +0300
Subject: [PATCH 10/24] ensureCollection pg tests
---
.../mysql-store/src/queries/add-column.ts | 4 +-
packages/mysql-store/src/queries/add-index.ts | 6 +-
.../src/queries/__fixtures__/users-table.ts | 48 +++++++-----
.../src/queries/add-column.ts | 11 ++-
.../postgresql-store/src/queries/add-index.ts | 11 ++-
.../src/queries/drop-index.ts | 5 +-
.../src/queries/list-table-statistics.ts | 20 ++++-
packages/postgresql-store/src/store.test.ts | 74 ++++++++++++++++++-
.../sql-store/src/logic/collections/ensure.ts | 36 ++++-----
packages/sql-store/src/queries/drop-column.ts | 8 +-
packages/sql-store/src/queries/drop-index.ts | 5 +-
11 files changed, 168 insertions(+), 60 deletions(-)
diff --git a/packages/mysql-store/src/queries/add-column.ts b/packages/mysql-store/src/queries/add-column.ts
index 0526579..4b0716a 100644
--- a/packages/mysql-store/src/queries/add-column.ts
+++ b/packages/mysql-store/src/queries/add-column.ts
@@ -1,8 +1,8 @@
import { SQLConnection } from '@neuledge/sql-store';
-import { StoreField } from '@neuledge/store';
+import { StoreCollection, StoreField } from '@neuledge/store';
export const addColumn = async (
- tableName: string,
+ collection: StoreCollection,
field: StoreField,
connection: SQLConnection,
): Promise => {};
diff --git a/packages/mysql-store/src/queries/add-index.ts b/packages/mysql-store/src/queries/add-index.ts
index aec306f..699b8d7 100644
--- a/packages/mysql-store/src/queries/add-index.ts
+++ b/packages/mysql-store/src/queries/add-index.ts
@@ -1,10 +1,10 @@
import { SQLConnection, indexColumns } from '@neuledge/sql-store';
-import { StoreIndex } from '@neuledge/store';
+import { StoreCollection, StoreIndex } from '@neuledge/store';
// FIXME handle if not exists on mysql
export const addIndex = async (
- tableName: string,
+ collection: StoreCollection,
index: StoreIndex,
connection: SQLConnection,
): Promise => {
@@ -12,6 +12,6 @@ export const addIndex = async (
`CREATE ${
index.unique ? 'UNIQUE INDEX' : 'INDEX'
} IF NOT EXISTS ? ON ? (${indexColumns(index)})`,
- [index.name, tableName],
+ [index.name, collection.name],
);
};
diff --git a/packages/postgresql-store/src/queries/__fixtures__/users-table.ts b/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
index baf5ff4..ad3fc41 100644
--- a/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
+++ b/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
@@ -15,6 +15,12 @@ export const usersTable_createSql = `CREATE TABLE IF NOT EXISTS ${usersTableName
CONSTRAINT ${usersTableName}_pkey PRIMARY KEY (id)
)`;
+export const usersTable_phoneAddSql = `ALTER TABLE ${usersTableName} ADD COLUMN phone VARCHAR(20)`;
+
+export const usersTable_emailIndexCreateSql = `CREATE UNIQUE INDEX IF NOT EXISTS ${usersTableName}_email_idx ON ${usersTableName} (email ASC)`;
+
+export const usersTable_phoneEmailIndexCreateSql = `CREATE INDEX IF NOT EXISTS ${usersTableName}_phone_email_idx ON ${usersTableName} (phone DESC, email ASC)`;
+
export const usersTable: PostgreSQLTable = { table_name: usersTableName };
export const usersCollection_slim: StoreCollection_Slim = {
@@ -78,9 +84,21 @@ export const usersTableColumns: PostgreSQLColumn[] = [
},
];
+export const usersTablePrimaryIndexes: PostgreSQLIndexAttribute[] = [
+ {
+ index_name: `${usersTableName}_id_idx`,
+ column_name: 'id',
+ seq_in_index: 1,
+ direction: 'ASC',
+ nulls: 'LAST',
+ is_unique: true,
+ is_primary: true,
+ },
+];
+
export const usersTableIndexes: PostgreSQLIndexAttribute[] = [
{
- index_name: 'idx_email',
+ index_name: `${usersTableName}_email_idx`,
column_name: 'email',
seq_in_index: 1,
direction: 'ASC',
@@ -89,7 +107,7 @@ export const usersTableIndexes: PostgreSQLIndexAttribute[] = [
is_primary: false,
},
{
- index_name: 'idx_phone_email',
+ index_name: `${usersTableName}_phone_email_idx`,
column_name: 'phone',
seq_in_index: 1,
direction: 'DESC',
@@ -98,7 +116,7 @@ export const usersTableIndexes: PostgreSQLIndexAttribute[] = [
is_primary: false,
},
{
- index_name: 'idx_phone_email',
+ index_name: `${usersTableName}_phone_email_idx`,
column_name: 'email',
seq_in_index: 2,
direction: 'ASC',
@@ -106,15 +124,7 @@ export const usersTableIndexes: PostgreSQLIndexAttribute[] = [
is_unique: false,
is_primary: false,
},
- {
- index_name: 'users_pkey',
- column_name: 'id',
- seq_in_index: 1,
- direction: 'ASC',
- nulls: 'LAST',
- is_unique: true,
- is_primary: true,
- },
+ ...usersTablePrimaryIndexes,
];
export const usersCollection: StoreCollection = {
@@ -170,25 +180,25 @@ export const usersCollection: StoreCollection = {
},
},
primaryKey: {
- name: `${usersTableName}_pkey`,
+ name: 'id',
fields: { id: { sort: 'asc' } },
unique: 'primary',
auto: 'increment',
},
indexes: {
- [`${usersTableName}_pkey`]: {
- name: `${usersTableName}_pkey`,
+ id: {
+ name: 'id',
fields: { id: { sort: 'asc' } },
unique: 'primary',
auto: 'increment',
},
- idx_email: {
- name: 'idx_email',
+ email: {
+ name: 'email',
fields: { email: { sort: 'asc' } },
unique: true,
},
- idx_phone_email: {
- name: 'idx_phone_email',
+ phone_email: {
+ name: 'phone_email',
fields: { phone: { sort: 'desc' }, email: { sort: 'asc' } },
unique: false,
},
diff --git a/packages/postgresql-store/src/queries/add-column.ts b/packages/postgresql-store/src/queries/add-column.ts
index 6b1800b..0e973d3 100644
--- a/packages/postgresql-store/src/queries/add-column.ts
+++ b/packages/postgresql-store/src/queries/add-column.ts
@@ -2,10 +2,17 @@ import { SQLConnection } from '@neuledge/sql-store';
import { StoreCollection, StoreField } from '@neuledge/store';
export const addColumn = async (
- tableName: string,
+ collection: StoreCollection,
field: StoreField,
connection: SQLConnection,
-): Promise => {};
+): Promise => {
+ await connection.query(
+ `ALTER TABLE ${collection.name} ADD COLUMN ${getColumnDefinition(
+ field,
+ collection,
+ )}`,
+ );
+};
export const getColumnDefinition = (
field: StoreField,
diff --git a/packages/postgresql-store/src/queries/add-index.ts b/packages/postgresql-store/src/queries/add-index.ts
index 75dd409..b05b8e6 100644
--- a/packages/postgresql-store/src/queries/add-index.ts
+++ b/packages/postgresql-store/src/queries/add-index.ts
@@ -1,15 +1,14 @@
import { SQLConnection, indexColumns } from '@neuledge/sql-store';
-import { StoreIndex } from '@neuledge/store';
+import { StoreCollection, StoreIndex } from '@neuledge/store';
export const addIndex = async (
- tableName: string,
+ collection: StoreCollection,
index: StoreIndex,
connection: SQLConnection,
): Promise => {
await connection.query(
- `CREATE ${
- index.unique ? 'UNIQUE INDEX' : 'INDEX'
- } IF NOT EXISTS ? (${indexColumns(index)})`,
- [`${tableName}_${index.name}_idx`],
+ `CREATE ${index.unique ? 'UNIQUE INDEX' : 'INDEX'} IF NOT EXISTS ${
+ collection.name
+ }_${index.name}_idx ON ${collection.name} (${indexColumns(index)})`,
);
};
diff --git a/packages/postgresql-store/src/queries/drop-index.ts b/packages/postgresql-store/src/queries/drop-index.ts
index 595fa2d..13ff480 100644
--- a/packages/postgresql-store/src/queries/drop-index.ts
+++ b/packages/postgresql-store/src/queries/drop-index.ts
@@ -1,11 +1,12 @@
+import { StoreCollection } from '@neuledge/store';
import { SQLConnection } from '@neuledge/sql-store';
export const dropIndex = async (
- tableName: string,
+ collection: StoreCollection,
index: string,
connection: SQLConnection,
): Promise => {
await connection.query(`DROP INDEX IF EXISTS ?`, [
- `${tableName}_${index}_idx`,
+ `${collection.name}_${index}_idx`,
]);
};
diff --git a/packages/postgresql-store/src/queries/list-table-statistics.ts b/packages/postgresql-store/src/queries/list-table-statistics.ts
index e6a2006..d020ef7 100644
--- a/packages/postgresql-store/src/queries/list-table-statistics.ts
+++ b/packages/postgresql-store/src/queries/list-table-statistics.ts
@@ -16,10 +16,22 @@ export interface PostgreSQLIndexAttribute {
export const listIndexAttributes = async (
tableName: string,
connection: SQLConnection,
-): Promise =>
- connection.query(listIndexAttributes_sql, [
- tableName,
- ]);
+): Promise => {
+ const res = await connection.query(
+ listIndexAttributes_sql,
+ [tableName],
+ );
+
+ for (const row of res) {
+ if (!row.index_name.startsWith(`${tableName}_`)) continue;
+
+ row.index_name = row.index_name
+ .slice(tableName.length + 1)
+ .replace(/_idx$/, '');
+ }
+
+ return res;
+};
export const listIndexAttributes_sql = `SELECT
irel.relname AS index_name,
diff --git a/packages/postgresql-store/src/store.test.ts b/packages/postgresql-store/src/store.test.ts
index 95d2b85..dec57f1 100644
--- a/packages/postgresql-store/src/store.test.ts
+++ b/packages/postgresql-store/src/store.test.ts
@@ -10,7 +10,11 @@ import {
usersTableColumns,
usersTableIndexes,
usersTableName,
+ usersTablePrimaryIndexes,
usersTable_createSql,
+ usersTable_emailIndexCreateSql,
+ usersTable_phoneAddSql,
+ usersTable_phoneEmailIndexCreateSql,
} from './queries/__fixtures__/users-table';
import { PostgreSQLStore } from './store';
@@ -48,7 +52,7 @@ describe('store', () => {
let query: jest.Mock;
beforeEach(() => {
- query = jest.fn();
+ query = jest.fn().mockRejectedValue(new Error('unexpected query call'));
store = new PostgreSQLStore({
client: { query } as never,
@@ -110,6 +114,74 @@ describe('store', () => {
usersTableName,
]);
});
+
+ it('should create a new table', async () => {
+ query.mockResolvedValueOnce({ rows: [] });
+ query.mockResolvedValueOnce({ rows: usersTableColumns });
+ query.mockResolvedValueOnce({ rows: usersTablePrimaryIndexes });
+ query.mockResolvedValueOnce({ rows: [] });
+ query.mockResolvedValueOnce({ rows: [] });
+
+ await store.ensureCollection({
+ collection: usersCollection,
+ fields: Object.values(usersCollection.fields),
+ indexes: Object.values(usersCollection.indexes),
+ });
+
+ expect(query).toHaveBeenCalledTimes(5);
+ expect(query).toHaveBeenNthCalledWith(1, usersTable_createSql, []);
+ expect(query).toHaveBeenNthCalledWith(2, listTableColumns_sql, [
+ usersTableName,
+ ]);
+ expect(query).toHaveBeenNthCalledWith(3, listIndexAttributes_sql, [
+ usersTableName,
+ ]);
+ expect(query).toHaveBeenNthCalledWith(
+ 4,
+ usersTable_emailIndexCreateSql,
+ [],
+ );
+ expect(query).toHaveBeenNthCalledWith(
+ 5,
+ usersTable_phoneEmailIndexCreateSql,
+ [],
+ );
+ });
+
+ it('should create fill missing fields and indexes', async () => {
+ query.mockResolvedValueOnce({ rows: [] });
+ query.mockResolvedValueOnce({
+ rows: usersTableColumns.filter((c) => c.column_name !== 'phone'),
+ });
+ query.mockResolvedValueOnce({
+ rows: usersTableIndexes.filter(
+ (i) => !i.index_name.includes('phone'),
+ ),
+ });
+ query.mockResolvedValueOnce({ rows: [] });
+ query.mockResolvedValueOnce({ rows: [] });
+
+ await store.ensureCollection({
+ collection: usersCollection,
+ fields: Object.values(usersCollection.fields),
+ indexes: Object.values(usersCollection.indexes),
+ });
+
+ expect(query).toHaveBeenCalledTimes(5);
+ expect(query).toHaveBeenNthCalledWith(1, usersTable_createSql, []);
+ expect(query).toHaveBeenNthCalledWith(2, listTableColumns_sql, [
+ usersTableName,
+ ]);
+ expect(query).toHaveBeenNthCalledWith(3, listIndexAttributes_sql, [
+ usersTableName,
+ ]);
+ expect(query).toHaveBeenNthCalledWith(4, usersTable_phoneAddSql, []);
+ expect(query).toHaveBeenNthCalledWith(
+ 5,
+ usersTable_phoneEmailIndexCreateSql,
+ [],
+ );
+ });
});
});
});
diff --git a/packages/sql-store/src/logic/collections/ensure.ts b/packages/sql-store/src/logic/collections/ensure.ts
index 42ff986..abe72ac 100644
--- a/packages/sql-store/src/logic/collections/ensure.ts
+++ b/packages/sql-store/src/logic/collections/ensure.ts
@@ -19,22 +19,22 @@ export interface EnsureCollectionQueries {
connection: SQLConnection,
) => Promise;
addIndex: (
- tableName: string,
+ collection: StoreCollection,
index: StoreIndex,
connection: SQLConnection,
) => Promise;
addColumn: (
- tableName: string,
+ collection: StoreCollection,
field: StoreField,
connection: SQLConnection,
) => Promise;
dropIndex?: (
- tableName: string,
+ collection: StoreCollection,
index: string,
connection: SQLConnection,
) => Promise;
dropColumn?: (
- tableName: string,
+ collection: StoreCollection,
field: string,
connection: SQLConnection,
) => Promise;
@@ -57,18 +57,17 @@ export const ensureCollection = async <
): Promise => {
await createTableIfNotExists(options.collection, connection);
- const tableName = options.collection.name;
const asyncLimit = pLimit(4);
await Promise.all(
options.dropIndexes?.map((index) =>
- asyncLimit(() => dropIndex(tableName, index, connection)),
+ asyncLimit(() => dropIndex(options.collection, index, connection)),
) || [],
);
await Promise.all(
options.dropFields?.map((field) =>
- asyncLimit(() => dropColumn(tableName, field, connection)),
+ asyncLimit(() => dropColumn(options.collection, field, connection)),
) || [],
);
@@ -83,16 +82,19 @@ export const ensureCollection = async <
// the engine to ensure that new columns are nullable if inserted after the
// collection has been created and this is the current implementation.
- await Promise.all([
- ...(options.indexes
- ?.filter((index) => !collection.indexes[index.name])
- .map((index) =>
- asyncLimit(() => addIndex(tableName, index, connection)),
- ) || []),
- ...(options.fields
+ await Promise.all(
+ options.fields
?.filter((field) => !collection.fields[field.name])
.map((field) =>
- asyncLimit(() => addColumn(tableName, field, connection)),
- ) || []),
- ]);
+ asyncLimit(() => addColumn(collection, field, connection)),
+ ) || [],
+ );
+
+ await Promise.all(
+ options.indexes
+ ?.filter((index) => !collection.indexes[index.name])
+ .map((index) =>
+ asyncLimit(() => addIndex(collection, index, connection)),
+ ) || [],
+ );
};
diff --git a/packages/sql-store/src/queries/drop-column.ts b/packages/sql-store/src/queries/drop-column.ts
index 6d775a8..54dd8ec 100644
--- a/packages/sql-store/src/queries/drop-column.ts
+++ b/packages/sql-store/src/queries/drop-column.ts
@@ -1,9 +1,13 @@
+import { StoreCollection } from '@neuledge/store';
import { SQLConnection } from './connection';
export const dropColumn = async (
- tableName: string,
+ collection: StoreCollection,
field: string,
connection: SQLConnection,
): Promise => {
- await connection.query(`ALTER TABLE ? DROP COLUMN ?`, [tableName, field]);
+ await connection.query(`ALTER TABLE ? DROP COLUMN ?`, [
+ collection.name,
+ field,
+ ]);
};
diff --git a/packages/sql-store/src/queries/drop-index.ts b/packages/sql-store/src/queries/drop-index.ts
index 642fb80..7c46308 100644
--- a/packages/sql-store/src/queries/drop-index.ts
+++ b/packages/sql-store/src/queries/drop-index.ts
@@ -1,9 +1,10 @@
+import { StoreCollection } from '@neuledge/store';
import { SQLConnection } from './connection';
export const dropIndex = async (
- tableName: string,
+ collection: StoreCollection,
index: string,
connection: SQLConnection,
): Promise => {
- await connection.query(`DROP INDEX ? ON ?`, [index, tableName]);
+ await connection.query(`DROP INDEX ? ON ?`, [index, collection.name]);
};
From f7788d96ba3a4532272afdc09ed1c62c3eea7d7b Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Sat, 15 Apr 2023 01:12:51 +0300
Subject: [PATCH 11/24] fix pg pk with desc columns
---
packages/postgresql-store/src/queries/create-table.ts | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/packages/postgresql-store/src/queries/create-table.ts b/packages/postgresql-store/src/queries/create-table.ts
index 6a0a9e1..184048d 100644
--- a/packages/postgresql-store/src/queries/create-table.ts
+++ b/packages/postgresql-store/src/queries/create-table.ts
@@ -1,6 +1,7 @@
import { SQLConnection } from '@neuledge/sql-store';
import { StoreCollection } from '@neuledge/store';
import { getColumnDefinition } from './add-column';
+import { addIndex } from './add-index';
export const createTableIfNotExists = async (
collection: StoreCollection,
@@ -17,6 +18,13 @@ export const createTableIfNotExists = async (
)`,
);
- // FIXME add unique constraints if primary key has descending fields
+ // add unique constraints if primary key has descending fields
// https://stackoverflow.com/a/45604459/518153
+ if (
+ Object.values(collection.primaryKey.fields).some(
+ (field) => field.sort === 'desc',
+ )
+ ) {
+ await addIndex(collection, collection.primaryKey, connection);
+ }
};
From d1e1fb1ba02c24d3a1866851c2b8edd41c8d9826 Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Sat, 15 Apr 2023 01:17:42 +0300
Subject: [PATCH 12/24] test dropCollection pg
---
.../src/queries/__fixtures__/users-table.ts | 2 +-
packages/postgresql-store/src/store.test.ts | 14 ++++++++++++++
packages/sql-store/src/queries/drop-table.ts | 4 +++-
3 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/packages/postgresql-store/src/queries/__fixtures__/users-table.ts b/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
index ad3fc41..d55ca00 100644
--- a/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
+++ b/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
@@ -1,7 +1,7 @@
import { StoreCollection, StoreCollection_Slim } from '@neuledge/store';
import { PostgreSQLTable } from '../list-tables';
import { PostgreSQLColumn } from '../list-table-columns';
-import { PostgreSQLIndexAttribute } from '..';
+import { PostgreSQLIndexAttribute } from '../list-table-statistics';
export const usersTableName = 'users';
diff --git a/packages/postgresql-store/src/store.test.ts b/packages/postgresql-store/src/store.test.ts
index dec57f1..1a8e824 100644
--- a/packages/postgresql-store/src/store.test.ts
+++ b/packages/postgresql-store/src/store.test.ts
@@ -1,3 +1,4 @@
+import { dropTableIfExists_sql } from '@neuledge/sql-store';
import {
listIndexAttributes_sql,
listTableColumns_sql,
@@ -183,5 +184,18 @@ describe('store', () => {
);
});
});
+
+ describe('.dropCollection()', () => {
+ it('should be able to drop a collection', async () => {
+ query.mockResolvedValueOnce({ rows: [] });
+
+ await store.dropCollection({ collection: usersCollection });
+
+ expect(query).toHaveBeenCalledTimes(1);
+ expect(query).toHaveBeenCalledWith(dropTableIfExists_sql, [
+ usersTableName,
+ ]);
+ });
+ });
});
});
diff --git a/packages/sql-store/src/queries/drop-table.ts b/packages/sql-store/src/queries/drop-table.ts
index a6a0689..874609c 100644
--- a/packages/sql-store/src/queries/drop-table.ts
+++ b/packages/sql-store/src/queries/drop-table.ts
@@ -3,4 +3,6 @@ import { SQLConnection } from './connection';
export const dropTableIfExists = async (
connection: SQLConnection,
tableName: string,
-): Promise => connection.query(`DROP TABLE IF EXISTS ?`, [tableName]);
+): Promise => connection.query(dropTableIfExists_sql, [tableName]);
+
+export const dropTableIfExists_sql = `DROP TABLE IF EXISTS ?`;
From e466c71fe4323cbb2eacb3b1b82701b1b623a4ff Mon Sep 17 00:00:00 2001
From: Moshe Simantov
Date: Sun, 16 Apr 2023 19:42:23 +0300
Subject: [PATCH 13/24] pg literal escapes
---
.../mysql-store/src/queries/add-column.ts | 4 +-
packages/mysql-store/src/queries/add-index.ts | 15 +--
.../mysql-store/src/queries/connection.ts | 3 +
.../mysql-store/src/queries/create-table.ts | 15 +--
.../mysql-store/src/queries/drop-column.ts | 8 ++
.../mysql-store/src/queries/drop-index.ts | 8 ++
.../mysql-store/src/queries/drop-table.ts | 6 ++
packages/mysql-store/src/queries/index.ts | 5 +-
.../src/queries/list-table-columns.ts | 13 ++-
.../src/queries/list-table-statistics.ts | 13 ++-
.../mysql-store/src/queries/list-tables.ts | 11 ++-
packages/mysql-store/src/store.ts | 25 ++---
packages/postgresql-store/package.json | 8 +-
.../src/queries/__fixtures__/users-table.ts | 24 ++---
.../src/queries/add-column.ts | 14 +--
.../postgresql-store/src/queries/add-index.ts | 18 +++-
.../src/queries/connection.ts | 3 +
.../src/queries/create-table.ts | 17 ++--
.../src/queries/drop-column.ts | 15 +++
.../src/queries/drop-index.ts | 11 ++-
.../src/queries/drop-table.ts | 9 ++
.../postgresql-store/src/queries/index.ts | 2 +
.../src/queries/list-table-columns.ts | 10 +-
.../src/queries/list-table-statistics.ts | 12 +--
.../src/queries/list-tables.ts | 8 +-
packages/postgresql-store/src/store.test.ts | 19 ++--
packages/postgresql-store/src/store.ts | 20 ++--
packages/sql-store/src/index.ts | 1 -
.../src/logic/collections/describe.ts | 93 +++++++++++--------
.../sql-store/src/logic/collections/drop.ts | 10 +-
.../sql-store/src/logic/collections/ensure.ts | 61 ++++++------
.../sql-store/src/logic/collections/list.ts | 11 +--
packages/sql-store/src/queries/connection.ts | 3 -
packages/sql-store/src/queries/drop-column.ts | 13 ---
packages/sql-store/src/queries/drop-index.ts | 10 --
packages/sql-store/src/queries/drop-table.ts | 8 --
.../sql-store/src/queries/index-columns.ts | 6 --
packages/sql-store/src/queries/index.ts | 5 -
yarn.lock | 10 ++
39 files changed, 292 insertions(+), 255 deletions(-)
create mode 100644 packages/mysql-store/src/queries/connection.ts
create mode 100644 packages/mysql-store/src/queries/drop-column.ts
create mode 100644 packages/mysql-store/src/queries/drop-index.ts
create mode 100644 packages/mysql-store/src/queries/drop-table.ts
create mode 100644 packages/postgresql-store/src/queries/connection.ts
create mode 100644 packages/postgresql-store/src/queries/drop-column.ts
create mode 100644 packages/postgresql-store/src/queries/drop-table.ts
delete mode 100644 packages/sql-store/src/queries/connection.ts
delete mode 100644 packages/sql-store/src/queries/drop-column.ts
delete mode 100644 packages/sql-store/src/queries/drop-index.ts
delete mode 100644 packages/sql-store/src/queries/drop-table.ts
delete mode 100644 packages/sql-store/src/queries/index-columns.ts
delete mode 100644 packages/sql-store/src/queries/index.ts
diff --git a/packages/mysql-store/src/queries/add-column.ts b/packages/mysql-store/src/queries/add-column.ts
index 4b0716a..166da74 100644
--- a/packages/mysql-store/src/queries/add-column.ts
+++ b/packages/mysql-store/src/queries/add-column.ts
@@ -1,8 +1,8 @@
-import { SQLConnection } from '@neuledge/sql-store';
import { StoreCollection, StoreField } from '@neuledge/store';
+import { MySQLConnection } from './connection';
export const addColumn = async (
+ connection: MySQLConnection,
collection: StoreCollection,
field: StoreField,
- connection: SQLConnection,
): Promise => {};
diff --git a/packages/mysql-store/src/queries/add-index.ts b/packages/mysql-store/src/queries/add-index.ts
index 699b8d7..4cb7f21 100644
--- a/packages/mysql-store/src/queries/add-index.ts
+++ b/packages/mysql-store/src/queries/add-index.ts
@@ -1,17 +1,8 @@
-import { SQLConnection, indexColumns } from '@neuledge/sql-store';
import { StoreCollection, StoreIndex } from '@neuledge/store';
-
-// FIXME handle if not exists on mysql
+import { MySQLConnection } from './connection';
export const addIndex = async (
+ connection: MySQLConnection,
collection: StoreCollection,
index: StoreIndex,
- connection: SQLConnection,
-): Promise => {
- await connection.query(
- `CREATE ${
- index.unique ? 'UNIQUE INDEX' : 'INDEX'
- } IF NOT EXISTS ? ON ? (${indexColumns(index)})`,
- [index.name, collection.name],
- );
-};
+): Promise => {};
diff --git a/packages/mysql-store/src/queries/connection.ts b/packages/mysql-store/src/queries/connection.ts
new file mode 100644
index 0000000..d65c071
--- /dev/null
+++ b/packages/mysql-store/src/queries/connection.ts
@@ -0,0 +1,3 @@
+import { Connection, Pool } from 'mysql';
+
+export type MySQLConnection = Pick;
diff --git a/packages/mysql-store/src/queries/create-table.ts b/packages/mysql-store/src/queries/create-table.ts
index af8195e..1deeaf3 100644
--- a/packages/mysql-store/src/queries/create-table.ts
+++ b/packages/mysql-store/src/queries/create-table.ts
@@ -1,16 +1,7 @@
-import { SQLConnection } from '@neuledge/sql-store';
import { StoreCollection } from '@neuledge/store';
-
-// FIXME handle if not exists on mysql
+import { MySQLConnection } from './connection';
export const createTableIfNotExists = async (
+ connection: MySQLConnection,
collection: StoreCollection,
- connection: SQLConnection,
-): Promise => {
- await connection.query(
- `CREATE TABLE IF NOT EXISTS ? (
- ${/* FIXME add columns */ ''}
- )`,
- [collection.name],
- );
-};
+): Promise => {};
diff --git a/packages/mysql-store/src/queries/drop-column.ts b/packages/mysql-store/src/queries/drop-column.ts
new file mode 100644
index 0000000..594fdad
--- /dev/null
+++ b/packages/mysql-store/src/queries/drop-column.ts
@@ -0,0 +1,8 @@
+import { StoreCollection } from '@neuledge/store';
+import { MySQLConnection } from './connection';
+
+export const dropColumn = async (
+ connection: MySQLConnection,
+ collection: StoreCollection,
+ field: string,
+): Promise => {};
diff --git a/packages/mysql-store/src/queries/drop-index.ts b/packages/mysql-store/src/queries/drop-index.ts
new file mode 100644
index 0000000..0d05fb1
--- /dev/null
+++ b/packages/mysql-store/src/queries/drop-index.ts
@@ -0,0 +1,8 @@
+import { StoreCollection } from '@neuledge/store';
+import { MySQLConnection } from './connection';
+
+export const dropIndex = async (
+ connection: MySQLConnection,
+ collection: StoreCollection,
+ index: string,
+): Promise => {};
diff --git a/packages/mysql-store/src/queries/drop-table.ts b/packages/mysql-store/src/queries/drop-table.ts
new file mode 100644
index 0000000..14716fb
--- /dev/null
+++ b/packages/mysql-store/src/queries/drop-table.ts
@@ -0,0 +1,6 @@
+import { MySQLConnection } from './connection';
+
+export const dropTableIfExists = async (
+ connection: MySQLConnection,
+ tableName: string,
+): Promise => {};
diff --git a/packages/mysql-store/src/queries/index.ts b/packages/mysql-store/src/queries/index.ts
index 45b41a4..fe9dba7 100644
--- a/packages/mysql-store/src/queries/index.ts
+++ b/packages/mysql-store/src/queries/index.ts
@@ -1,6 +1,9 @@
export * from './add-column';
export * from './add-index';
export * from './create-table';
-export * from './list-tables';
+export * from './drop-column';
+export * from './drop-index';
+export * from './drop-table';
export * from './list-table-columns';
export * from './list-table-statistics';
+export * from './list-tables';
diff --git a/packages/mysql-store/src/queries/list-table-columns.ts b/packages/mysql-store/src/queries/list-table-columns.ts
index 3e97bba..bfb3018 100644
--- a/packages/mysql-store/src/queries/list-table-columns.ts
+++ b/packages/mysql-store/src/queries/list-table-columns.ts
@@ -1,5 +1,5 @@
import { StoreShapeType } from '@neuledge/store';
-import { SQLConnection } from '@neuledge/sql-store';
+import { MySQLConnection } from './connection';
/**
* A table column from the information_schema.columns table.
@@ -15,14 +15,17 @@ export interface MySQLColumn {
}
export const listTableColumns = async (
+ connection: MySQLConnection,
tableName: string,
- connection: SQLConnection,
): Promise =>
- connection.query(
- `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, (is_nullable = 'YES') AS is_nullable, extra LIKE '%auto_increment%' AS is_auto_increment
+ new Promise((resolve, reject) =>
+ connection.query(
+ `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, (is_nullable = 'YES') AS is_nullable, extra LIKE '%auto_increment%' AS is_auto_increment
FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = ?`,
- [tableName],
+ [tableName],
+ (error, results) => (error ? reject(error) : resolve(results)),
+ ),
);
export const dataTypeMap: Record = {
diff --git a/packages/mysql-store/src/queries/list-table-statistics.ts b/packages/mysql-store/src/queries/list-table-statistics.ts
index fb1509e..f6816f0 100644
--- a/packages/mysql-store/src/queries/list-table-statistics.ts
+++ b/packages/mysql-store/src/queries/list-table-statistics.ts
@@ -1,4 +1,4 @@
-import { SQLConnection } from '@neuledge/sql-store';
+import { MySQLConnection } from './connection';
/**
* A table statistic row from the information_schema.statistics table.
@@ -13,13 +13,16 @@ export interface MySQLIndexAttribute {
}
export const listIndexAttributes = async (
+ connection: MySQLConnection,
tableName: string,
- connection: SQLConnection,
): Promise =>
- connection.query(
- `SELECT index_name, column_name, seq_in_index, CASE collation WHEN 'A' THEN 'ASC' ELSE 'DESC' END AS direction, non_unique, (index_name == 'PRIMARY') as is_primary
+ new Promise((resolve, reject) =>
+ connection.query(
+ `SELECT index_name, column_name, seq_in_index, CASE collation WHEN 'A' THEN 'ASC' ELSE 'DESC' END AS direction, non_unique, (index_name == 'PRIMARY') as is_primary
FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = ?
ORDER BY index_name, seq_in_index`,
- [tableName],
+ [tableName],
+ (error, results) => (error ? reject(error) : resolve(results)),
+ ),
);
diff --git a/packages/mysql-store/src/queries/list-tables.ts b/packages/mysql-store/src/queries/list-tables.ts
index 23fdb8f..ee6a9ee 100644
--- a/packages/mysql-store/src/queries/list-tables.ts
+++ b/packages/mysql-store/src/queries/list-tables.ts
@@ -1,4 +1,4 @@
-import { SQLConnection } from '@neuledge/sql-store';
+import { MySQLConnection } from './connection';
/**
* The tables in the database. This is a view of the `information_schema.tables` table.
@@ -8,8 +8,11 @@ export interface MySQLTable {
}
export const listTables = async (
- connection: SQLConnection,
+ connection: MySQLConnection,
): Promise =>
- connection.query(
- `SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()`,
+ new Promise((resolve, reject) =>
+ connection.query(
+ `SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()`,
+ (error, results) => (error ? reject(error) : resolve(results)),
+ ),
);
diff --git a/packages/mysql-store/src/store.ts b/packages/mysql-store/src/store.ts
index 522f486..741b69f 100644
--- a/packages/mysql-store/src/store.ts
+++ b/packages/mysql-store/src/store.ts
@@ -23,9 +23,11 @@ import {
createTableIfNotExists,
addIndex,
addColumn,
+ dropColumn,
+ dropIndex,
+ dropTableIfExists,
} from './queries';
import {
- SQLConnection,
describeCollection,
dropCollection,
ensureCollection,
@@ -37,27 +39,18 @@ export type MySQLStoreClient = Pool | Connection;
export type MySQLStoreOptions = PoolConfig | { client: MySQLStoreClient };
export class MySQLStore implements Store {
- private client: MySQLStoreClient;
- private connection: SQLConnection;
+ private connection: MySQLStoreClient;
constructor(options: MySQLStoreOptions) {
- this.client = 'client' in options ? options.client : createPool(options);
-
- this.connection = {
- query: (sql, values) =>
- new Promise((resolve, reject) =>
- this.client.query(sql, values, (error, results) =>
- error ? reject(error) : resolve(results),
- ),
- ),
- };
+ this.connection =
+ 'client' in options ? options.client : createPool(options);
}
// connection methods
async close(): Promise {
await new Promise((resolve, reject) =>
- this.client.end((error) => (error ? reject(error) : resolve())),
+ this.connection.end((error) => (error ? reject(error) : resolve())),
);
}
@@ -82,6 +75,8 @@ export class MySQLStore implements Store {
createTableIfNotExists,
addIndex,
addColumn,
+ dropColumn,
+ dropIndex,
listTableColumns,
listIndexAttributes,
dataTypeMap,
@@ -89,7 +84,7 @@ export class MySQLStore implements Store {
}
async dropCollection(options: StoreDropCollectionOptions): Promise {
- return dropCollection(options, this.connection);
+ return dropCollection(options, this.connection, { dropTableIfExists });
}
async find(options: StoreFindOptions): Promise> {
diff --git a/packages/postgresql-store/package.json b/packages/postgresql-store/package.json
index 4d1091f..cf2d864 100644
--- a/packages/postgresql-store/package.json
+++ b/packages/postgresql-store/package.json
@@ -38,9 +38,13 @@
"lint:strict": "yarn lint --max-warnings 0"
},
"dependencies": {
- "@neuledge/store": "^0.2.0",
"@neuledge/sql-store": "^0.0.0",
+ "@neuledge/store": "^0.2.0",
"@types/pg": "^8.6.6",
- "pg": "^8.10.0"
+ "pg": "^8.10.0",
+ "pg-format": "^1.0.4"
+ },
+ "devDependencies": {
+ "@types/pg-format": "^1.0.2"
}
}
diff --git a/packages/postgresql-store/src/queries/__fixtures__/users-table.ts b/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
index d55ca00..9d21cb5 100644
--- a/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
+++ b/packages/postgresql-store/src/queries/__fixtures__/users-table.ts
@@ -5,21 +5,23 @@ import { PostgreSQLIndexAttribute } from '../list-table-statistics';
export const usersTableName = 'users';
-export const usersTable_createSql = `CREATE TABLE IF NOT EXISTS ${usersTableName} (
- id BIGSERIAL NOT NULL,
- name VARCHAR(50),
- email VARCHAR(100) NOT NULL,
- phone VARCHAR(20),
- created_at TIMESTAMP NOT NULL,
- updated_at TIMESTAMP NOT NULL,
- CONSTRAINT ${usersTableName}_pkey PRIMARY KEY (id)
+export const usersTable_dropSql = `DROP TABLE IF EXISTS '${usersTableName}'`;
+
+export const usersTable_createSql = `CREATE TABLE IF NOT EXISTS '${usersTableName}' (
+ 'id' BIGSERIAL NOT NULL,
+ 'name' VARCHAR(50),
+ 'email' VARCHAR(100) NOT NULL,
+ 'phone' VARCHAR(20),
+ 'created_at' TIMESTAMP NOT NULL,
+ 'updated_at' TIMESTAMP NOT NULL,
+ CONSTRAINT '${usersTableName}_pkey' PRIMARY KEY ('id')
)`;
-export const usersTable_phoneAddSql = `ALTER TABLE ${usersTableName} ADD COLUMN phone VARCHAR(20)`;
+export const usersTable_phoneAddSql = `ALTER TABLE '${usersTableName}' ADD COLUMN 'phone' VARCHAR(20)`;
-export const usersTable_emailIndexCreateSql = `CREATE UNIQUE INDEX IF NOT EXISTS ${usersTableName}_email_idx ON ${usersTableName} (email ASC)`;
+export const usersTable_emailIndexCreateSql = `CREATE UNIQUE INDEX IF NOT EXISTS '${usersTableName}_email_idx' ON '${usersTableName}' ('email' ASC)`;
-export const usersTable_phoneEmailIndexCreateSql = `CREATE INDEX IF NOT EXISTS ${usersTableName}_phone_email_idx ON ${usersTableName} (phone DESC, email ASC)`;
+export const usersTable_phoneEmailIndexCreateSql = `CREATE INDEX IF NOT EXISTS '${usersTableName}_phone_email_idx' ON '${usersTableName}' ('phone' DESC, 'email' ASC)`;
export const usersTable: PostgreSQLTable = { table_name: usersTableName };
diff --git a/packages/postgresql-store/src/queries/add-column.ts b/packages/postgresql-store/src/queries/add-column.ts
index 0e973d3..31226e8 100644
--- a/packages/postgresql-store/src/queries/add-column.ts
+++ b/packages/postgresql-store/src/queries/add-column.ts
@@ -1,16 +1,16 @@
-import { SQLConnection } from '@neuledge/sql-store';
import { StoreCollection, StoreField } from '@neuledge/store';
+import { PostgreSQLConnection } from './connection';
+import format from 'pg-format';
export const addColumn = async (
+ connection: PostgreSQLConnection,
collection: StoreCollection,
field: StoreField,
- connection: SQLConnection,
): Promise => {
await connection.query(
- `ALTER TABLE ${collection.name} ADD COLUMN ${getColumnDefinition(
- field,
- collection,
- )}`,
+ `ALTER TABLE ${format.literal(
+ collection.name,
+ )} ADD COLUMN ${getColumnDefinition(field, collection)}`,
);
};
@@ -18,7 +18,7 @@ export const getColumnDefinition = (
field: StoreField,
collection: StoreCollection,
): string =>
- `${field.name} ${getColumnDataType(field, collection)}${
+ `${format.literal(field.name)} ${getColumnDataType(field, collection)}${
field.nullable ? '' : ' NOT NULL'
}`;
diff --git a/packages/postgresql-store/src/queries/add-index.ts b/packages/postgresql-store/src/queries/add-index.ts
index b05b8e6..0e83e11 100644
--- a/packages/postgresql-store/src/queries/add-index.ts
+++ b/packages/postgresql-store/src/queries/add-index.ts
@@ -1,14 +1,22 @@
-import { SQLConnection, indexColumns } from '@neuledge/sql-store';
import { StoreCollection, StoreIndex } from '@neuledge/store';
+import { PostgreSQLConnection } from './connection';
+import format from 'pg-format';
export const addIndex = async (
+ connection: PostgreSQLConnection,
collection: StoreCollection,
index: StoreIndex,
- connection: SQLConnection,
): Promise => {
await connection.query(
- `CREATE ${index.unique ? 'UNIQUE INDEX' : 'INDEX'} IF NOT EXISTS ${
- collection.name
- }_${index.name}_idx ON ${collection.name} (${indexColumns(index)})`,
+ `CREATE ${
+ index.unique ? 'UNIQUE INDEX' : 'INDEX'
+ } IF NOT EXISTS ${format.literal(
+ `${collection.name}_${index.name}_idx`,
+ )} ON ${format.literal(collection.name)} (${Object.entries(index.fields)
+ .map(
+ ([field, { sort }]) =>
+ `${format.literal(field)} ${sort === 'desc' ? 'DESC' : 'ASC'}`,
+ )
+ .join(', ')})`,
);
};
diff --git a/packages/postgresql-store/src/queries/connection.ts b/packages/postgresql-store/src/queries/connection.ts
new file mode 100644
index 0000000..2c4239d
--- /dev/null
+++ b/packages/postgresql-store/src/queries/connection.ts
@@ -0,0 +1,3 @@
+import { Client, Pool } from 'pg';
+
+export type PostgreSQLConnection = Pick;
diff --git a/packages/postgresql-store/src/queries/create-table.ts b/packages/postgresql-store/src/queries/create-table.ts
index 184048d..95d6e5d 100644
--- a/packages/postgresql-store/src/queries/create-table.ts
+++ b/packages/postgresql-store/src/queries/create-table.ts
@@ -1,20 +1,23 @@
-import { SQLConnection } from '@neuledge/sql-store';
import { StoreCollection } from '@neuledge/store';
import { getColumnDefinition } from './add-column';
import { addIndex } from './add-index';
+import { PostgreSQLConnection } from './connection';
+import format from 'pg-format';
export const createTableIfNotExists = async (
+ connection: PostgreSQLConnection,
collection: StoreCollection,
- connection: SQLConnection,
): Promise => {
await connection.query(
- `CREATE TABLE IF NOT EXISTS ${collection.name} (
+ `CREATE TABLE IF NOT EXISTS ${format.literal(collection.name)} (
${Object.values(collection.fields)
.map((field) => getColumnDefinition(field, collection))
.join(',\n ')},
- CONSTRAINT ${collection.name}_pkey PRIMARY KEY (${Object.keys(
- collection.primaryKey.fields,
- ).join(', ')})
+ CONSTRAINT ${format.literal(
+ `${collection.name}_pkey`,
+ )} PRIMARY KEY (${Object.keys(collection.primaryKey.fields)
+ .map((val) => format.literal(val))
+ .join(', ')})
)`,
);
@@ -25,6 +28,6 @@ export const createTableIfNotExists = async (
(field) => field.sort === 'desc',
)
) {
- await addIndex(collection, collection.primaryKey, connection);
+ await addIndex(connection, collection, collection.primaryKey);
}
};
diff --git a/packages/postgresql-store/src/queries/drop-column.ts b/packages/postgresql-store/src/queries/drop-column.ts
new file mode 100644
index 0000000..663d5e4
--- /dev/null
+++ b/packages/postgresql-store/src/queries/drop-column.ts
@@ -0,0 +1,15 @@
+import { StoreCollection } from '@neuledge/store';
+import { PostgreSQLConnection } from './connection';
+import format from 'pg-format';
+
+export const dropColumn = async (
+ connection: PostgreSQLConnection,
+ collection: StoreCollection,
+ field: string,
+): Promise => {
+ await connection.query(
+ `ALTER TABLE ${format.literal(
+ collection.name,
+ )} DROP COLUMN ${format.literal(field)}`,
+ );
+};
diff --git a/packages/postgresql-store/src/queries/drop-index.ts b/packages/postgresql-store/src/queries/drop-index.ts
index 13ff480..63d48de 100644
--- a/packages/postgresql-store/src/queries/drop-index.ts
+++ b/packages/postgresql-store/src/queries/drop-index.ts
@@ -1,12 +1,13 @@
import { StoreCollection } from '@neuledge/store';
-import { SQLConnection } from '@neuledge/sql-store';
+import { PostgreSQLConnection } from './connection';
+import format from 'pg-format';
export const dropIndex = async (
+ connection: PostgreSQLConnection,
collection: StoreCollection,
index: string,
- connection: SQLConnection,
): Promise => {
- await connection.query(`DROP INDEX IF EXISTS ?`, [
- `${collection.name}_${index}_idx`,
- ]);
+ await connection.query(
+ `DROP INDEX IF EXISTS ${format.literal(`${collection.name}_${index}_idx`)}`,
+ );
};
diff --git a/packages/postgresql-store/src/queries/drop-table.ts b/packages/postgresql-store/src/queries/drop-table.ts
new file mode 100644
index 0000000..3eee5e9
--- /dev/null
+++ b/packages/postgresql-store/src/queries/drop-table.ts
@@ -0,0 +1,9 @@
+import format from 'pg-format';
+import { PostgreSQLConnection } from './connection';
+
+export const dropTableIfExists = async (
+ connection: PostgreSQLConnection,
+ tableName: string,
+): Promise => {
+ await connection.query(`DROP TABLE IF EXISTS ${format.literal(tableName)}`);
+};
diff --git a/packages/postgresql-store/src/queries/index.ts b/packages/postgresql-store/src/queries/index.ts
index 66fa894..671584a 100644
--- a/packages/postgresql-store/src/queries/index.ts
+++ b/packages/postgresql-store/src/queries/index.ts
@@ -1,6 +1,8 @@
export * from './add-column';
export * from './add-index';
+export * from './drop-column';
export * from './drop-index';
+export * from './drop-table';
export * from './create-table';
export * from './list-tables';
export * from './list-table-columns';
diff --git a/packages/postgresql-store/src/queries/list-table-columns.ts b/packages/postgresql-store/src/queries/list-table-columns.ts
index 029775b..625e032 100644
--- a/packages/postgresql-store/src/queries/list-table-columns.ts
+++ b/packages/postgresql-store/src/queries/list-table-columns.ts
@@ -1,5 +1,5 @@
import { StoreShapeType } from '@neuledge/store';
-import { SQLConnection } from '@neuledge/sql-store';
+import { PostgreSQLConnection } from './connection';
/**
* A table column from the information_schema.columns table.
@@ -15,14 +15,16 @@ export interface PostgreSQLColumn {
}
export const listTableColumns = async (
+ connection: PostgreSQLConnection,
tableName: string,
- connection: SQLConnection,
): Promise =>
- connection.query(listTableColumns_sql, [tableName]);
+ connection
+ .query(listTableColumns_sql, [tableName])
+ .then((result) => result.rows);
export const listTableColumns_sql = `SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, (is_nullable = 'YES') as is_nullable, column_default LIKE 'nextval(%)' AS is_auto_increment
FROM information_schema.columns
-WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_name = ?`;
+WHERE table_catalog = current_database() AND table_schema = current_schema() AND table_name = $1`;
// https://www.postgresql.org/docs/current/datatype.html
export const dataTypeMap: Record = {
diff --git a/packages/postgresql-store/src/queries/list-table-statistics.ts b/packages/postgresql-store/src/queries/list-table-statistics.ts
index d020ef7..6a23fde 100644
--- a/packages/postgresql-store/src/queries/list-table-statistics.ts
+++ b/packages/postgresql-store/src/queries/list-table-statistics.ts
@@ -1,4 +1,4 @@
-import { SQLConnection } from '@neuledge/sql-store';
+import { PostgreSQLConnection } from './connection';
/**
* A table statistic row from the information_schema.statistics table.
@@ -14,15 +14,15 @@ export interface PostgreSQLIndexAttribute {
}
export const listIndexAttributes = async (
+ connection: PostgreSQLConnection,
tableName: string,
- connection: SQLConnection,
): Promise => {
- const res = await connection.query(
+ const { rows } = await connection.query(
listIndexAttributes_sql,
[tableName],
);
- for (const row of res) {
+ for (const row of rows) {
if (!row.index_name.startsWith(`${tableName}_`)) continue;
row.index_name = row.index_name
@@ -30,7 +30,7 @@ export const listIndexAttributes = async (
.replace(/_idx$/, '');
}
- return res;
+ return rows;
};
export const listIndexAttributes_sql = `SELECT
@@ -49,5 +49,5 @@ CROSS JOIN LATERAL unnest (i.indkey) WITH ORDINALITY AS c (colnum, ordinality)
LEFT JOIN LATERAL unnest (i.indoption) WITH ORDINALITY AS o (option, ordinality)
ON c.ordinality = o.ordinality
JOIN pg_attribute AS a ON trel.oid = a.attrelid AND a.attnum = c.colnum
-WHERE tnsp.nspname = current_schema() AND trel.relname = ?
+WHERE tnsp.nspname = current_schema() AND trel.relname = $1
ORDER BY index_name, seq_in_index`;
diff --git a/packages/postgresql-store/src/queries/list-tables.ts b/packages/postgresql-store/src/queries/list-tables.ts
index e4aad48..b5c29ee 100644
--- a/packages/postgresql-store/src/queries/list-tables.ts
+++ b/packages/postgresql-store/src/queries/list-tables.ts
@@ -1,4 +1,4 @@
-import { SQLConnection } from '@neuledge/sql-store';
+import { PostgreSQLConnection } from './connection';
/**
* The tables in the database. This is a view of the `information_schema.tables` table.
@@ -8,9 +8,11 @@ export interface PostgreSQLTable {
}
export const listTables = async (
- connection: SQLConnection,
+ connection: PostgreSQLConnection,
): Promise =>
- connection.query(listTables_sql);
+ connection
+ .query(listTables_sql)
+ .then((result) => result.rows);
export const listTables_sql = `SELECT table_name
FROM information_schema.tables
diff --git a/packages/postgresql-store/src/store.test.ts b/packages/postgresql-store/src/store.test.ts
index 1a8e824..426c464 100644
--- a/packages/postgresql-store/src/store.test.ts
+++ b/packages/postgresql-store/src/store.test.ts
@@ -1,4 +1,3 @@
-import { dropTableIfExists_sql } from '@neuledge/sql-store';
import {
listIndexAttributes_sql,
listTableColumns_sql,
@@ -13,6 +12,7 @@ import {
usersTableName,
usersTablePrimaryIndexes,
usersTable_createSql,
+ usersTable_dropSql,
usersTable_emailIndexCreateSql,
usersTable_phoneAddSql,
usersTable_phoneEmailIndexCreateSql,
@@ -67,7 +67,7 @@ describe('store', () => {
const collections = await store.listCollections();
expect(query).toHaveBeenCalledTimes(1);
- expect(query).toHaveBeenCalledWith(listTables_sql, []);
+ expect(query).toHaveBeenCalledWith(listTables_sql);
expect(collections).toEqual([usersCollection_slim]);
});
@@ -107,7 +107,7 @@ describe('store', () => {
});
expect(query).toHaveBeenCalledTimes(3);
- expect(query).toHaveBeenNthCalledWith(1, usersTable_createSql, []);
+ expect(query).toHaveBeenNthCalledWith(1, usersTable_createSql);
expect(query).toHaveBeenNthCalledWith(2, listTableColumns_sql, [
usersTableName,
]);
@@ -130,7 +130,7 @@ describe('store', () => {
});
expect(query).toHaveBeenCalledTimes(5);
- expect(query).toHaveBeenNthCalledWith(1, usersTable_createSql, []);
+ expect(query).toHaveBeenNthCalledWith(1, usersTable_createSql);
expect(query).toHaveBeenNthCalledWith(2, listTableColumns_sql, [
usersTableName,
]);
@@ -140,12 +140,10 @@ describe('store', () => {
expect(query).toHaveBeenNthCalledWith(
4,
usersTable_emailIndexCreateSql,
- [],
);
expect(query).toHaveBeenNthCalledWith(
5,
usersTable_phoneEmailIndexCreateSql,
- [],
);
});
@@ -169,18 +167,17 @@ describe('store', () => {
});
expect(query).toHaveBeenCalledTimes(5);
- expect(query).toHaveBeenNthCalledWith(1, usersTable_createSql, []);
+ expect(query).toHaveBeenNthCalledWith(1, usersTable_createSql);
expect(query).toHaveBeenNthCalledWith(2, listTableColumns_sql, [
usersTableName,
]);
expect(query).toHaveBeenNthCalledWith(3, listIndexAttributes_sql, [
usersTableName,
]);
- expect(query).toHaveBeenNthCalledWith(4, usersTable_phoneAddSql, []);
+ expect(query).toHaveBeenNthCalledWith(4, usersTable_phoneAddSql);
expect(query).toHaveBeenNthCalledWith(
5,
usersTable_phoneEmailIndexCreateSql,
- [],
);
});
});
@@ -192,9 +189,7 @@ describe('store', () => {
await store.dropCollection({ collection: usersCollection });
expect(query).toHaveBeenCalledTimes(1);
- expect(query).toHaveBeenCalledWith(dropTableIfExists_sql, [
- usersTableName,
- ]);
+ expect(query).toHaveBeenCalledWith(usersTable_dropSql);
});
});
});
diff --git a/packages/postgresql-store/src/store.ts b/packages/postgresql-store/src/store.ts
index 9956eb2..3e6e4e2 100644
--- a/packages/postgresql-store/src/store.ts
+++ b/packages/postgresql-store/src/store.ts
@@ -8,6 +8,8 @@ import {
addIndex,
createTableIfNotExists,
addColumn,
+ dropColumn,
+ dropTableIfExists,
} from './queries';
import {
Store,
@@ -30,7 +32,6 @@ import {
describeCollection,
listCollections,
ensureCollection,
- SQLConnection,
} from '@neuledge/sql-store';
export type PostgreSQLStoreClient = Client | Pool;
@@ -42,24 +43,16 @@ export type PostgreSQLStoreOptions =
};
export class PostgreSQLStore implements Store {
- private client: PostgreSQLStoreClient;
- private connection: SQLConnection;
+ private connection: PostgreSQLStoreClient;
constructor(options: PostgreSQLStoreOptions) {
- this.client = 'client' in options ? options.client : new Pool(options);
-
- this.connection = {
- query: (sql, values) =>
- this.client
- .query(sql, values ?? [])
- .then((result) => result.rows as never),
- };
+ this.connection = 'client' in options ? options.client : new Pool(options);
}
// connection methods
async close(): Promise {
- await this.client.end();
+ await this.connection.end();
}
// store methods
@@ -84,6 +77,7 @@ export class PostgreSQLStore implements Store {
addIndex,
addColumn,
dropIndex,
+ dropColumn,
listTableColumns,
listIndexAttributes,
dataTypeMap,
@@ -91,7 +85,7 @@ export class PostgreSQLStore implements Store {
}
async dropCollection(options: StoreDropCollectionOptions): Promise {
- return dropCollection(options, this.connection);
+ return dropCollection(options, this.connection, { dropTableIfExists });
}
async find(options: StoreFindOptions): Promise> {
diff --git a/packages/sql-store/src/index.ts b/packages/sql-store/src/index.ts
index d941abe..ef390d6 100644
--- a/packages/sql-store/src/index.ts
+++ b/packages/sql-store/src/index.ts
@@ -1,3 +1,2 @@
export * from './logic';
export * from './mappers';
-export * from './queries';
diff --git a/packages/sql-store/src/logic/collections/describe.ts b/packages/sql-store/src/logic/collections/describe.ts
index e01ff38..9a26217 100644
--- a/packages/sql-store/src/logic/collections/describe.ts
+++ b/packages/sql-store/src/logic/collections/describe.ts
@@ -5,7 +5,6 @@ import {
toStoreField,
toStoreIndex,
} from '@/mappers';
-import { SQLConnection } from '@/queries';
import {
StoreCollection,
StoreDescribeCollectionOptions,
@@ -14,49 +13,33 @@ import {
} from '@neuledge/store';
export interface DescribeCollectionQueries<
- C extends SQLColumn,
- I extends SQLIndexAttribute & Omit,
+ Connection,
+ Column extends SQLColumn,
+ IndexAttribute extends SQLIndexAttribute & Omit,
> {
- listTableColumns: (name: string, connection: SQLConnection) => Promise;
- listIndexAttributes: (
+ listTableColumns(connection: Connection, name: string): Promise;
+ listIndexAttributes(
+ connection: Connection,
name: string,
- connection: SQLConnection,
- ) => Promise;
+ ): Promise;
dataTypeMap: Record;
}
export const describeCollection = async <
- C extends SQLColumn,
- I extends SQLIndexAttribute & Omit,
+ Connection,
+ Column extends SQLColumn,
+ IndexAttribute extends SQLIndexAttribute & Omit,
>(
options: StoreDescribeCollectionOptions,
- connection: SQLConnection,
- {
- listTableColumns,
- listIndexAttributes,
- dataTypeMap,
- }: DescribeCollectionQueries,
+ connection: Connection,
+ queries: DescribeCollectionQueries,
): Promise => {
- const { name } = options.collection;
-
- const [columns, indexAttributes] = await Promise.all([
- listTableColumns(name, connection),
- listIndexAttributes(name, connection),
- ]);
-
- const columnMap = Object.fromEntries(
- columns.map((column) => [column.column_name, column]),
+ const { name, fields, indexColumns } = await getCollectionDetails(
+ options,
+ connection,
+ queries,
);
- const fields = Object.fromEntries(
- columns.map((column) => [
- column.column_name,
- toStoreField(dataTypeMap, column),
- ]),
- );
-
- const indexColumns = groupIndexColumns(columnMap, indexAttributes);
-
let primaryKey: string | undefined;
const indexes = Object.fromEntries(
indexColumns.map((columns) => {
@@ -84,12 +67,48 @@ export const describeCollection = async <
};
};
+const getCollectionDetails = async <
+ Connection,
+ Column extends SQLColumn,
+ IndexAttribute extends SQLIndexAttribute & Omit,
+>(
+ options: StoreDescribeCollectionOptions,
+ connection: Connection,
+ {
+ listTableColumns,
+ listIndexAttributes,
+ dataTypeMap,
+ }: DescribeCollectionQueries,
+) => {
+ const { name } = options.collection;
+
+ const [columns, indexAttributes] = await Promise.all([
+ listTableColumns(connection, name),
+ listIndexAttributes(connection, name),
+ ]);
+
+ const columnMap = Object.fromEntries(
+ columns.map((column) => [column.column_name, column]),
+ );
+
+ const fields = Object.fromEntries(
+ columns.map((column) => [
+ column.column_name,
+ toStoreField(dataTypeMap, column),
+ ]),
+ );
+
+ const indexColumns = groupIndexColumns(columnMap, indexAttributes);
+
+ return { name, fields, indexColumns };
+};
+
const groupIndexColumns = <
- C extends SQLColumn,
- I extends SQLIndexAttribute & Omit,
+ Column extends SQLColumn,
+ IndexAttribute extends SQLIndexAttribute & Omit,
>(
- columnMap: Record,
- indexAttributes: I[],
+ columnMap: Record,
+ indexAttributes: IndexAttribute[],
): SQLIndexColumn[][] => {
const groupMap: Record = {};
diff --git a/packages/sql-store/src/logic/collections/drop.ts b/packages/sql-store/src/logic/collections/drop.ts
index f7e9d1b..ba3241c 100644
--- a/packages/sql-store/src/logic/collections/drop.ts
+++ b/packages/sql-store/src/logic/collections/drop.ts
@@ -1,9 +1,13 @@
-import { SQLConnection, dropTableIfExists } from '@/queries';
import { StoreDropCollectionOptions } from '@neuledge/store';
-export const dropCollection = async (
+export interface DropCollectionQueries {
+ dropTableIfExists(connection: Connection, name: string): Promise;
+}
+
+export const dropCollection = async (
options: StoreDropCollectionOptions,
- connection: SQLConnection,
+ connection: Connection,
+ { dropTableIfExists }: DropCollectionQueries,
): Promise => {
await dropTableIfExists(connection, options.collection.name);
};
diff --git a/packages/sql-store/src/logic/collections/ensure.ts b/packages/sql-store/src/logic/collections/ensure.ts
index abe72ac..38f0827 100644
--- a/packages/sql-store/src/logic/collections/ensure.ts
+++ b/packages/sql-store/src/logic/collections/ensure.ts
@@ -1,8 +1,3 @@
-import {
- SQLConnection,
- dropColumn as dropColumnDefault,
- dropIndex as dropIndexDefault,
-} from '@/queries';
import pLimit from 'p-limit';
import {
StoreCollection,
@@ -13,61 +8,63 @@ import {
import { SQLColumn, SQLIndexAttribute, SQLIndexColumn } from '@/mappers';
import { DescribeCollectionQueries, describeCollection } from './describe';
-export interface EnsureCollectionQueries {
- createTableIfNotExists: (
+export interface EnsureCollectionQueries {
+ createTableIfNotExists(
+ connection: Connection,
collection: StoreCollection,
- connection: SQLConnection,
- ) => Promise;
- addIndex: (
+ ): Promise;
+ addIndex(
+ connection: Connection,
collection: StoreCollection,
index: StoreIndex,
- connection: SQLConnection,
- ) => Promise;
- addColumn: (
+ ): Promise;
+ addColumn(
+ connection: Connection,
collection: StoreCollection,
field: StoreField,
- connection: SQLConnection,
- ) => Promise;
- dropIndex?: (
+ ): Promise;
+ dropIndex(
+ connection: Connection,
collection: StoreCollection,
index: string,
- connection: SQLConnection,
- ) => Promise;
- dropColumn?: (
+ ): Promise;
+ dropColumn(
+ connection: Connection,
collection: StoreCollection,
field: string,
- connection: SQLConnection,
- ) => Promise;
+ ): Promise;
}
export const ensureCollection = async <
- C extends SQLColumn,
- I extends SQLIndexAttribute & Omit,
+ Connection,
+ Column extends SQLColumn,
+ IndexAttribute extends SQLIndexAttribute & Omit,
>(
options: StoreEnsureCollectionOptions,
- connection: SQLConnection,
+ connection: Connection,
{
createTableIfNotExists,
addIndex,
addColumn,
- dropIndex = dropIndexDefault,
- dropColumn = dropColumnDefault,
+ dropIndex,
+ dropColumn,
...describeCollectionQueries
- }: EnsureCollectionQueries & DescribeCollectionQueries