@travetto/model-sql 2.2.6 → 3.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,109 +10,29 @@ npm install @travetto/model-sql
10
10
 
11
11
  **Install: Specific SQL Client: mysql**
12
12
  ```bash
13
- npm install mysql
13
+ npm install @travetto/model-mysql
14
14
  ```
15
15
 
16
16
  or
17
17
 
18
- **Install: Specific SQL Client: postgres**
18
+ **Install: Specific SQL Client: mysql**
19
19
  ```bash
20
- npm install pg
20
+ npm install @travetto/model-postgres
21
21
  ```
22
22
 
23
- This module provides a [SQL](https://en.wikipedia.org/wiki/SQL)-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module. This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [SQL](https://en.wikipedia.org/wiki/SQL) databases. In development mode, the [SQLModelService](https://github.com/travetto/travetto/tree/main/module/model-sql/src/service.ts#L38) will also modify the database schema in real time to minimize impact to development.
23
+ or
24
24
 
25
- The schema generated will not generally map to existing tables as it is attempting to produce a document store like experience on top of
26
- a [SQL](https://en.wikipedia.org/wiki/SQL) database. Every table generated will have a `path_id` which determines it's location in the document hierarchy as well as sub tables will have a `parent_path_id` to associate records with the parent values.
25
+ **Install: Specific SQL Client: mysql**
26
+ ```bash
27
+ npm install @travetto/model-sqlite
28
+ ```
27
29
 
28
30
  The current SQL client support stands at:
29
31
 
30
32
  * [MySQL](https://www.mysql.com/) - 5.6 and 5.7
31
33
  * [Postgres](https://postgresql.org) - 11+
34
+ * [SQLite](https://www.sqlite.org/) - (bettersqlite 7.6+)
32
35
  * `SQL Server` - Currently unsupported
33
36
  * `Oracle` - Currently unsupported
34
37
 
35
38
  **Note**: Wider client support will roll out as usage increases.
36
-
37
- Supported features:
38
-
39
- * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11)
40
- * [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/service/bulk.ts#L23)
41
- * [Query Crud](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/crud.ts#L11)
42
- * [Facet](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/facet.ts#L12)
43
- * [Query](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/query.ts#L10)
44
- * [Suggest](https://github.com/travetto/travetto/tree/main/module/model-query/src/service/suggest.ts#L12)
45
-
46
- Out of the box, by installing the module, everything should be wired up by default.If you need to customize any aspect of the source
47
- or config, you can override and register it with the [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support.") module.
48
-
49
-
50
- **Code: Wiring up a custom Model Source**
51
- ```typescript
52
- import { AsyncContext } from '@travetto/context';
53
- import { InjectableFactory } from '@travetto/di';
54
-
55
- import { SQLModelService, SQLModelConfig } from '@travetto/model-sql';
56
- import { MySQLDialect } from '@travetto/model-sql/src/dialect/mysql/dialect';
57
-
58
- export class Init {
59
- @InjectableFactory({ primary: true })
60
- static getModelService(ctx: AsyncContext, conf: SQLModelConfig) {
61
- return new SQLModelService(ctx, conf, new MySQLDialect(ctx, conf));
62
- }
63
- }
64
- ```
65
-
66
- where the [SQLModelConfig](https://github.com/travetto/travetto/tree/main/module/model-sql/src/config.ts#L7) is defined by:
67
-
68
-
69
- **Code: Structure of SQLModelConfig**
70
- ```typescript
71
- import { Config } from '@travetto/config';
72
-
73
- /**
74
- * SQL Model Config
75
- */
76
- @Config('model.sql')
77
- export class SQLModelConfig {
78
- /**
79
- * Host to connect to
80
- */
81
- host = '127.0.0.1';
82
- /**
83
- * Default port
84
- */
85
- port = 0;
86
- /**
87
- * Username
88
- */
89
- user = '';
90
- /**
91
- * Password
92
- */
93
- password = '';
94
- /**
95
- * Table prefix
96
- */
97
- namespace = '';
98
- /**
99
- * Database name
100
- */
101
- database = 'app';
102
- /**
103
- * Auto schema creation
104
- */
105
- autoCreate?: boolean;
106
- /**
107
- * Db version
108
- */
109
- version = '';
110
- /**
111
- * Raw client options
112
- */
113
- options = {};
114
- }
115
- ```
116
-
117
- Additionally, you can see that the class is registered with the [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L9) annotation, and so these values can be overridden using the
118
- standard [Configuration](https://github.com/travetto/travetto/tree/main/module/config#readme "Environment-aware config management using yaml files")resolution paths.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@travetto/model-sql",
3
3
  "displayName": "SQL Model Service",
4
- "version": "2.2.6",
4
+ "version": "3.0.0-rc.2",
5
5
  "description": "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.",
6
6
  "keywords": [
7
7
  "sql",
@@ -20,7 +20,8 @@
20
20
  "files": [
21
21
  "index.ts",
22
22
  "src",
23
- "support"
23
+ "support",
24
+ "test-support"
24
25
  ],
25
26
  "main": "index.ts",
26
27
  "repository": {
@@ -28,21 +29,16 @@
28
29
  "directory": "module/model-sql"
29
30
  },
30
31
  "dependencies": {
31
- "@travetto/config": "^2.2.4",
32
- "@travetto/context": "^2.2.6",
33
- "@travetto/model": "^2.2.4",
34
- "@travetto/model-query": "2.2.4"
32
+ "@travetto/config": "^3.0.0-rc.2",
33
+ "@travetto/context": "^3.0.0-rc.2",
34
+ "@travetto/model": "^3.0.0-rc.2",
35
+ "@travetto/model-query": "3.0.0-rc.2"
35
36
  },
36
37
  "devDependencies": {
37
- "@travetto/app": "^2.2.5"
38
+ "@travetto/app": "^3.0.0-rc.2"
38
39
  },
39
- "optionalPeerDependencies": {
40
- "@types/mysql": "^2.15.21",
41
- "@types/pg": "^8.6.5",
42
- "mysql": "^2.18.1",
43
- "pg": "^8.7.3",
44
- "better-sqlite3": "^7.6.2",
45
- "@types/better-sqlite3": "^7.6.0"
40
+ "docDependencies": {
41
+ "@travetto/model-mysql": 1
46
42
  },
47
43
  "publishConfig": {
48
44
  "access": "public"
@@ -230,15 +230,15 @@ export class SQLUtil {
230
230
 
231
231
  for (const [k, v] of Object.entries(select)) {
232
232
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
233
- const typedKey = k as keyof typeof select;
234
- if (!Util.isPlainObject(select[typedKey]) && localMap[k]) {
233
+ const sk = k as string;
234
+ if (!Util.isPlainObject(select[k]) && localMap[sk]) {
235
235
  if (!v) {
236
236
  if (toGet.size === 0) {
237
237
  toGet = new Set(SchemaRegistry.get(cls).views[AllViewⲐ].fields);
238
238
  }
239
- toGet.delete(k);
239
+ toGet.delete(sk);
240
240
  } else {
241
- toGet.add(k);
241
+ toGet.add(sk);
242
242
  }
243
243
  }
244
244
  }
@@ -0,0 +1,102 @@
1
+ import * as assert from 'assert';
2
+
3
+ import { Schema, FieldConfig } from '@travetto/schema';
4
+ import { Suite, Test } from '@travetto/test';
5
+ import { BaseModelSuite } from '@travetto/model/test-support/base';
6
+
7
+ import { VisitStack } from '../src/internal/util';
8
+ import { SQLModelService } from '../src/service';
9
+
10
+ @Schema()
11
+ class User {
12
+ id: string;
13
+ name: string;
14
+ }
15
+
16
+ @Schema()
17
+ class WhereTypeAB {
18
+ c: number;
19
+ }
20
+
21
+ @Schema()
22
+ class WhereTypeA {
23
+ d: number;
24
+ b: WhereTypeAB;
25
+ }
26
+
27
+ @Schema()
28
+ class WhereTypeD {
29
+ e: boolean;
30
+ }
31
+
32
+ @Schema()
33
+ class WhereTypeG {
34
+ z: string[];
35
+ }
36
+
37
+ @Schema()
38
+ class WhereType {
39
+ a: WhereTypeA[];
40
+ d: WhereTypeD;
41
+ g: WhereTypeG;
42
+ name: number;
43
+ age: number;
44
+ }
45
+
46
+ @Suite()
47
+ export abstract class BaseSQLTest extends BaseModelSuite<SQLModelService> {
48
+
49
+ get dialect() {
50
+ return this.service.then(s => s.client);
51
+ }
52
+
53
+ @Test()
54
+ async validateQuery() {
55
+ const qry = {
56
+ $and: [
57
+ { a: { b: { c: 5 } } },
58
+ { d: { e: true } },
59
+ {
60
+ $or: [{ name: 5 }, { age: 10 }]
61
+ },
62
+ { g: { z: { $in: ['a', 'b', 'c'] } } },
63
+ { a: { d: { $gt: 20 } } }
64
+ ]
65
+ };
66
+
67
+ const dct = await this.dialect;
68
+ dct.resolveName = (stack: VisitStack[]) => {
69
+ const field = stack[stack.length - 1] as FieldConfig;
70
+ const parent = stack[stack.length - 2] as FieldConfig;
71
+ return `${field.owner ? field.owner.name : parent.name}.${field.name}`;
72
+ };
73
+
74
+ const qryStr = dct.getWhereGroupingSQL(WhereType, qry);
75
+ assert(qryStr === "(WhereTypeAB.c = 5 AND WhereTypeD.e = TRUE AND (WhereType.name = 5 OR WhereType.age = 10) AND z.z IN ('a','b','c') AND WhereTypeA.d > 20)");
76
+ }
77
+
78
+ @Test()
79
+ async testRegEx() {
80
+ const dct = await this.dialect;
81
+ dct.resolveName = (stack: VisitStack[]) => {
82
+ const field = stack[stack.length - 1] as FieldConfig;
83
+ return `${field.owner?.name}.${field.name}`;
84
+ };
85
+
86
+ const out = dct.getWhereGroupingSQL(User, {
87
+ name: {
88
+ $regex: /google.$/
89
+ }
90
+ });
91
+
92
+ assert(out === `User.name ${dct.SQL_OPS.$regex} 'google.$'`);
93
+
94
+ const outBoundary = dct.getWhereGroupingSQL(User, {
95
+ name: {
96
+ $regex: /\bgoogle\b/
97
+ }
98
+ });
99
+
100
+ assert(outBoundary === `User.name ${dct.SQL_OPS.$regex} '${dct.regexWordBoundary}google${dct.regexWordBoundary}'`);
101
+ }
102
+ }
@@ -1,85 +0,0 @@
1
- // @file-if mysql
2
- import * as mysql from 'mysql';
3
-
4
- import { ShutdownManager } from '@travetto/base';
5
- import { AsyncContext } from '@travetto/context';
6
- import { ExistsError } from '@travetto/model/src/error/exists';
7
-
8
- import { Connection } from '../../connection/base';
9
- import { SQLModelConfig } from '../../config';
10
-
11
- /**
12
- * Connection support for mysql
13
- */
14
- export class MySQLConnection extends Connection<mysql.PoolConnection> {
15
-
16
- #pool: mysql.Pool;
17
- #config: SQLModelConfig;
18
-
19
- constructor(
20
- context: AsyncContext,
21
- config: SQLModelConfig
22
- ) {
23
- super(context);
24
- this.#config = config;
25
- }
26
-
27
- async init(): Promise<void> {
28
- this.#pool = mysql.createPool({
29
- user: this.#config.user,
30
- password: this.#config.password,
31
- database: this.#config.database,
32
- host: this.#config.host,
33
- port: this.#config.port,
34
- timezone: 'utc',
35
- typeCast: this.typeCast.bind(this),
36
- ...(this.#config.options || {})
37
- });
38
-
39
- // Close mysql
40
- ShutdownManager.onShutdown(this.constructor.ᚕid, () => new Promise(r => this.#pool.end(r)));
41
- }
42
-
43
- /**
44
- * Support some basic type support for JSON data
45
- */
46
- typeCast(field: Parameters<Exclude<mysql.TypeCast, boolean>>[0], next: () => unknown): unknown {
47
- const res = next();
48
- if (typeof res === 'string' && (field.type === 'JSON' || field.type === 'BLOB')) {
49
- if (res.charAt(0) === '{' && res.charAt(res.length - 1) === '}') {
50
- try {
51
- return JSON.parse(res);
52
- } catch { }
53
- }
54
- }
55
- return res;
56
- }
57
-
58
- async execute<T = unknown>(conn: mysql.Connection, query: string): Promise<{ count: number, records: T[] }> {
59
- return new Promise<{ count: number, records: T[] }>((res, rej) => {
60
- console.debug('Executing Query', { query });
61
- conn.query(query, (err, results, fields) => {
62
- if (err) {
63
- console.debug('Failed query', { error: err, query });
64
- if (err.message.startsWith('ER_DUP_ENTRY')) {
65
- rej(new ExistsError('query', query));
66
- } else {
67
- rej(err);
68
- }
69
- } else {
70
- const records: T[] = Array.isArray(results) ? [...results].map(v => ({ ...v })) : [{ ...results }];
71
- res({ records, count: results.affectedRows });
72
- }
73
- });
74
- });
75
- }
76
-
77
- acquire(): Promise<mysql.PoolConnection> {
78
- return new Promise<mysql.PoolConnection>((res, rej) =>
79
- this.#pool.getConnection((err, conn) => err ? rej(err) : res(conn)));
80
- }
81
-
82
- release(conn: mysql.PoolConnection): void {
83
- conn.release();
84
- }
85
- }
@@ -1,100 +0,0 @@
1
- // @file-if mysql
2
- import { FieldConfig } from '@travetto/schema';
3
- import { Injectable } from '@travetto/di';
4
- import { AsyncContext } from '@travetto/context';
5
- import { WhereClause } from '@travetto/model-query';
6
- import { Class } from '@travetto/base';
7
- import { ModelType } from '@travetto/model';
8
-
9
- import { SQLModelConfig } from '../../config';
10
- import { SQLDialect } from '../base';
11
- import { VisitStack } from '../../internal/util';
12
- import { MySQLConnection } from './connection';
13
-
14
- /**
15
- * MYSQL Dialect for the SQL Model Source
16
- */
17
- @Injectable()
18
- export class MySQLDialect extends SQLDialect {
19
-
20
- conn: MySQLConnection;
21
- tablePostfix = "COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB";
22
-
23
- constructor(context: AsyncContext, public config: SQLModelConfig) {
24
- super(config.namespace);
25
- this.conn = new MySQLConnection(context, config);
26
-
27
- // Customer operators
28
- Object.assign(this.SQL_OPS, {
29
- $regex: 'REGEXP BINARY',
30
- $iregex: 'REGEXP'
31
- });
32
-
33
- // Custom types
34
- Object.assign(this.COLUMN_TYPES, {
35
- TIMESTAMP: 'DATETIME(3)',
36
- JSON: 'TEXT'
37
- });
38
-
39
- // Word boundary
40
- this.regexWordBoundary = '([[:<:]]|[[:>:]])';
41
- // Field maxlength
42
- this.idField.minlength = this.idField.maxlength = { n: this.KEY_LEN };
43
-
44
- /**
45
- * Set string length limit based on version
46
- */
47
- if (/^5[.][56]/.test(this.config.version)) {
48
- this.DEFAULT_STRING_LEN = 191; // Mysql limitation with utf8 and keys
49
- }
50
- }
51
-
52
- /**
53
- * Compute hash
54
- */
55
- hash(value: string): string {
56
- return `SHA2('${value}', ${this.KEY_LEN * 4})`;
57
- }
58
-
59
- /**
60
- * Build identifier
61
- */
62
- ident(field: FieldConfig | string): string {
63
- return `\`${typeof field === 'string' ? field : field.name}\``;
64
- }
65
-
66
- /**
67
- * Create table, adding in specific engine options
68
- */
69
- override getCreateTableSQL(stack: VisitStack[]): string {
70
- return super.getCreateTableSQL(stack).replace(/;$/, ` ${this.tablePostfix};`);
71
- }
72
-
73
- /**
74
- * Define column modification
75
- */
76
- getModifyColumnSQL(stack: VisitStack[]): string {
77
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
78
- const field = stack[stack.length - 1] as FieldConfig;
79
- return `ALTER TABLE ${this.parentTable(stack)} MODIFY COLUMN ${this.getColumnDefinition(field)};`;
80
- }
81
-
82
- /**
83
- * Add root alias to delete clause
84
- */
85
- override getDeleteSQL(stack: VisitStack[], where?: WhereClause<unknown>): string {
86
- const sql = super.getDeleteSQL(stack, where);
87
- return sql.replace(/\bDELETE\b/g, `DELETE ${this.rootAlias}`);
88
- }
89
-
90
- /**
91
- * Suppress foreign key checks
92
- */
93
- override getTruncateAllTablesSQL<T extends ModelType>(cls: Class<T>): string[] {
94
- return [
95
- 'SET FOREIGN_KEY_CHECKS = 0;',
96
- ...super.getTruncateAllTablesSQL(cls),
97
- 'SET FOREIGN_KEY_CHECKS = 1;'
98
- ];
99
- }
100
- }
@@ -1,74 +0,0 @@
1
- // @file-if pg
2
- import * as pg from 'pg';
3
-
4
- import { ShutdownManager } from '@travetto/base';
5
- import { AsyncContext, WithAsyncContext } from '@travetto/context';
6
- import { ExistsError } from '@travetto/model';
7
-
8
- import { Connection } from '../../connection/base';
9
- import { SQLModelConfig } from '../../config';
10
-
11
- /**
12
- * Connection support for postgresql
13
- */
14
- export class PostgreSQLConnection extends Connection<pg.PoolClient> {
15
-
16
- #pool: pg.Pool;
17
- #config: SQLModelConfig;
18
-
19
- constructor(
20
- context: AsyncContext,
21
- config: SQLModelConfig
22
- ) {
23
- super(context);
24
- this.#config = config;
25
- }
26
-
27
- /**
28
- * Initializes connection and establishes crypto extension for use with hashing
29
- */
30
- @WithAsyncContext()
31
- async init(): Promise<void> {
32
- this.#pool = new pg.Pool({
33
- user: this.#config.user,
34
- password: this.#config.password,
35
- database: this.#config.database,
36
- host: this.#config.host,
37
- port: this.#config.port,
38
- parseInputDatesAsUTC: true,
39
- ...(this.#config.options || {})
40
- });
41
-
42
- await this.runWithActive(() =>
43
- this.runWithTransaction('required', () =>
44
- this.execute(this.active, 'CREATE EXTENSION IF NOT EXISTS pgcrypto;')
45
- )
46
- );
47
-
48
- // Close postgres
49
- ShutdownManager.onShutdown(this.constructor.ᚕid, () => this.#pool.end());
50
- }
51
-
52
- async execute<T = unknown>(conn: pg.PoolClient, query: string): Promise<{ count: number, records: T[] }> {
53
- console.debug('Executing query', { query });
54
- try {
55
- const out = await conn.query(query);
56
- const records: T[] = [...out.rows].map(v => ({ ...v }));
57
- return { count: out.rowCount, records };
58
- } catch (err) {
59
- if (err instanceof Error && err.message.includes('duplicate key value')) {
60
- throw new ExistsError('query', query);
61
- } else {
62
- throw err;
63
- }
64
- }
65
- }
66
-
67
- acquire(): Promise<pg.PoolClient> {
68
- return this.#pool.connect();
69
- }
70
-
71
- release(conn: pg.PoolClient): void {
72
- conn.release();
73
- }
74
- }
@@ -1,69 +0,0 @@
1
- // @file-if pg
2
- import { FieldConfig } from '@travetto/schema';
3
- import { Injectable } from '@travetto/di';
4
- import { AsyncContext } from '@travetto/context';
5
- import { ModelType } from '@travetto/model';
6
- import { Class } from '@travetto/base';
7
-
8
- import { SQLModelConfig } from '../../config';
9
- import { SQLDialect } from '../base';
10
- import { SQLUtil, VisitStack } from '../../internal/util';
11
- import { PostgreSQLConnection } from './connection';
12
-
13
- /**
14
- * Postgresql Dialect for the SQL Model Source
15
- */
16
- @Injectable()
17
- export class PostgreSQLDialect extends SQLDialect {
18
-
19
- conn: PostgreSQLConnection;
20
-
21
- constructor(context: AsyncContext, public config: SQLModelConfig) {
22
- super(config.namespace);
23
- this.conn = new PostgreSQLConnection(context, config);
24
-
25
- // Special operators
26
- Object.assign(this.SQL_OPS, {
27
- $regex: '~',
28
- $iregex: '~*'
29
- });
30
-
31
- // Special types
32
- Object.assign(this.COLUMN_TYPES, {
33
- JSON: 'json',
34
- TIMESTAMP: 'TIMESTAMP(6) WITH TIME ZONE'
35
- });
36
-
37
- // Word boundary
38
- this.regexWordBoundary = '\\y';
39
- }
40
-
41
- /**
42
- * How to hash
43
- */
44
- hash(value: string): string {
45
- return `encode(digest('${value}', 'sha1'), 'hex')`;
46
- }
47
-
48
- ident(field: FieldConfig | string): string {
49
- return `"${typeof field === 'string' ? field : field.name}"`;
50
- }
51
-
52
- /**
53
- * Define column modification
54
- */
55
- getModifyColumnSQL(stack: VisitStack[]): string {
56
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
57
- const field = stack[stack.length - 1] as FieldConfig;
58
- const type = this.getColumnType(field);
59
- const ident = this.ident(field.name);
60
- return `ALTER TABLE ${this.parentTable(stack)} ALTER COLUMN ${ident} TYPE ${type} USING (${ident}::${type});`;
61
- }
62
-
63
- /**
64
- * Suppress foreign key checks
65
- */
66
- override getTruncateAllTablesSQL<T extends ModelType>(cls: Class<T>): string[] {
67
- return [`TRUNCATE ${this.table(SQLUtil.classToStack(cls))} CASCADE;`];
68
- }
69
- }
@@ -1,98 +0,0 @@
1
- // @file-if better-sqlite3
2
- import * as sqlite3 from 'better-sqlite3';
3
- import Db = require('better-sqlite3');
4
- import * as pool from 'generic-pool';
5
-
6
- import { ShutdownManager, Util } from '@travetto/base';
7
- import { AsyncContext, WithAsyncContext } from '@travetto/context';
8
- import { ExistsError } from '@travetto/model';
9
- import { AppCache } from '@travetto/boot';
10
-
11
- import { Connection } from '../../connection/base';
12
- import { SQLModelConfig } from '../../config';
13
-
14
- /**
15
- * Connection support for Sqlite
16
- */
17
- export class SqliteConnection extends Connection<sqlite3.Database> {
18
-
19
- isolatedTransactions = false;
20
-
21
- #config: SQLModelConfig;
22
- #pool: pool.Pool<sqlite3.Database>;
23
-
24
- constructor(
25
- context: AsyncContext,
26
- config: SQLModelConfig
27
- ) {
28
- super(context);
29
- this.#config = config;
30
- }
31
-
32
- async #withRetries<T>(op: () => Promise<T>, retries = 10, delay = 250): Promise<T> {
33
- for (; ;) {
34
- try {
35
- return await op();
36
- } catch (err) {
37
- if (err instanceof Error && retries > 1 && err.message.includes('database is locked')) {
38
- console.error('Failed, and waiting', retries);
39
- await Util.wait(delay);
40
- retries -= 1;
41
- } else {
42
- throw err;
43
- }
44
- }
45
- }
46
- }
47
-
48
- /**
49
- * Initializes connection and establishes crypto extension for use with hashing
50
- */
51
- @WithAsyncContext()
52
- override async init(): Promise<void> {
53
- this.#pool = pool.createPool({
54
- create: () => this.#withRetries(async () => {
55
- const db = Db(AppCache.toEntryName('sqlite_db'),
56
- this.#config.options
57
- );
58
- await db.pragma('foreign_keys = ON');
59
- await db.pragma('journal_mode = WAL');
60
- db.function('regexp', (a, b) => new RegExp(a).test(b) ? 1 : 0);
61
- return db;
62
- }),
63
- destroy: async db => { db.close(); }
64
- }, { max: 1 });
65
-
66
- // Close postgres
67
- ShutdownManager.onShutdown(this.constructor.ᚕid, () => this.#pool.clear());
68
- }
69
-
70
- async execute<T = unknown>(conn: sqlite3.Database, query: string): Promise<{ count: number, records: T[] }> {
71
- return this.#withRetries(async () => {
72
- console.debug('Executing query', { query });
73
- try {
74
- const out = await conn.prepare(query)[query.trim().startsWith('SELECT') ? 'all' : 'run']();
75
- if (Array.isArray(out)) {
76
- const records: T[] = [...out].map(v => ({ ...v }));
77
- return { count: out.length, records };
78
- } else {
79
- return { count: out.changes, records: [] };
80
- }
81
- } catch (err) {
82
- if (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {
83
- throw new ExistsError('query', query);
84
- } else {
85
- throw err;
86
- }
87
- }
88
- });
89
- }
90
-
91
- async acquire(): Promise<Db.Database> {
92
- return this.#pool.acquire();
93
- }
94
-
95
- async release(db: sqlite3.Database): Promise<void> {
96
- return this.#pool.release(db);
97
- }
98
- }
@@ -1,80 +0,0 @@
1
- // @file-if better-sqlite3
2
- import { FieldConfig } from '@travetto/schema';
3
- import { Injectable } from '@travetto/di';
4
- import { AsyncContext } from '@travetto/context';
5
- import { WhereClause } from '@travetto/model-query';
6
-
7
- import { SQLModelConfig } from '../../config';
8
- import { SQLDialect } from '../base';
9
- import { VisitStack } from '../../internal/util';
10
- import { SqliteConnection } from './connection';
11
-
12
- /**
13
- * Sqlite Dialect for the SQL Model Source
14
- */
15
- @Injectable()
16
- export class SqliteDialect extends SQLDialect {
17
-
18
- conn: SqliteConnection;
19
-
20
- constructor(context: AsyncContext, public config: SQLModelConfig) {
21
- super(config.namespace);
22
- this.conn = new SqliteConnection(context, config);
23
-
24
- // Special operators
25
- Object.assign(this.SQL_OPS, {
26
- $regex: 'REGEXP',
27
- $ilike: undefined
28
- });
29
-
30
- // Special types
31
- Object.assign(this.COLUMN_TYPES, {
32
- JSON: 'TEXT',
33
- TIMESTAMP: 'INTEGER'
34
- });
35
- }
36
-
37
- override resolveDateValue(value: Date): string {
38
- return `${value.getTime()}`;
39
- }
40
-
41
- /**
42
- * How to hash
43
- */
44
- hash(value: string): string {
45
- return `hex('${value}')`;
46
- }
47
-
48
- /**
49
- * Build identifier
50
- */
51
- ident(field: FieldConfig | string): string {
52
- return `\`${typeof field === 'string' ? field : field.name}\``;
53
- }
54
-
55
- /**
56
- * Define column modification
57
- */
58
- getModifyColumnSQL(stack: VisitStack[]): string {
59
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
60
- const field = stack[stack.length - 1] as FieldConfig;
61
- const type = this.getColumnType(field);
62
- const ident = this.ident(field.name);
63
- return `ALTER TABLE ${this.parentTable(stack)} ALTER COLUMN ${ident} TYPE ${type} USING (${ident}::${type});`;
64
- }
65
-
66
- /**
67
- * Generate truncate SQL
68
- */
69
- override getTruncateTableSQL(stack: VisitStack[]): string {
70
- return `DELETE FROM ${this.table(stack)};`;
71
- }
72
-
73
- override getDeleteSQL(stack: VisitStack[], where?: WhereClause<unknown>): string {
74
- return super.getDeleteSQL(stack, where).replace(/_ROOT[.]?/g, '');
75
- }
76
-
77
- override getUpdateSQL(stack: VisitStack[], data: Record<string, unknown>, where?: WhereClause<unknown>): string {
78
- return super.getUpdateSQL(stack, data, where).replace(/_ROOT[.]?/g, '');
79
- }
80
- }
@@ -1,16 +0,0 @@
1
- // @file-if mysql
2
- import { EnvUtil } from '@travetto/boot';
3
- import type { Service } from '@travetto/command/bin/lib/service';
4
-
5
- const version = EnvUtil.get('TRV_SERVICE_MYSQL', '5.6');
6
-
7
- export const service: Service = {
8
- name: 'mysql',
9
- version,
10
- image: `mysql:${version}`,
11
- port: 3306,
12
- env: {
13
- MYSQL_ROOT_PASSWORD: 'password',
14
- MYSQL_DATABASE: 'app'
15
- },
16
- };
@@ -1,17 +0,0 @@
1
- // @file-if pg
2
- import { EnvUtil } from '@travetto/boot';
3
- import type { Service } from '@travetto/command/bin/lib/service';
4
-
5
- const version = EnvUtil.get('TRV_SERVICE_POSTGRESQL', '12.2');
6
-
7
- export const service: Service = {
8
- name: 'postgresql',
9
- version,
10
- port: 5432,
11
- image: `postgres:${version}-alpine`,
12
- env: {
13
- POSTGRES_USER: 'root',
14
- POSTGRES_PASSWORD: 'password',
15
- POSTGRES_DB: 'app'
16
- }
17
- };