@platformatic/sql-mapper 0.10.0 → 0.12.0

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/lib/entity.js CHANGED
@@ -7,6 +7,12 @@ const {
7
7
  tableName,
8
8
  sanitizeLimit
9
9
  } = require('./utils')
10
+ const { singularize } = require('inflected')
11
+
12
+ function lowerCaseFirst (str) {
13
+ str = str.toString()
14
+ return str.charAt(0).toLowerCase() + str.slice(1)
15
+ }
10
16
 
11
17
  function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema, useSchemaInName, limitConfig) {
12
18
  /* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
@@ -85,9 +91,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
85
91
  }
86
92
 
87
93
  let now
88
- if (autoTimestamp && fields.updated_at) {
94
+ if (autoTimestamp && fields[autoTimestamp.updatedAt]) {
89
95
  now = new Date()
90
- input.updated_at = now
96
+ input[autoTimestamp.updatedAt] = now
91
97
  }
92
98
  if (hasPrimaryKeys) { // update
93
99
  const res = await queries.updateOne(db, sql, table, schema, input, primaryKeys, fieldsToRetrieve)
@@ -100,10 +106,10 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
100
106
  }
101
107
 
102
108
  // insert
103
- if (autoTimestamp && fields.inserted_at) {
109
+ if (autoTimestamp && fields[autoTimestamp.createdAt]) {
104
110
  /* istanbul ignore next */
105
111
  now = now || new Date()
106
- input.inserted_at = now
112
+ input[autoTimestamp.createdAt] = now
107
113
  }
108
114
  const res = await queries.insertOne(db, sql, table, schema, input, primaryKeysTypes, fieldsToRetrieve)
109
115
  return fixOutput(res)
@@ -118,11 +124,11 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
118
124
  if (autoTimestamp) {
119
125
  const now = new Date()
120
126
  for (const input of inputs) {
121
- if (fields.inserted_at) {
122
- input.insertedAt = now
127
+ if (fields[autoTimestamp.createdAt]) {
128
+ input[autoTimestamp.createdAt] = now
123
129
  }
124
- if (fields.updated_at) {
125
- input.updatedAt = now
130
+ if (fields[autoTimestamp.updatedAt]) {
131
+ input[autoTimestamp.updatedAt] = now
126
132
  }
127
133
  }
128
134
  }
@@ -151,10 +157,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
151
157
  throw new Error('Input not provided.')
152
158
  }
153
159
  const input = fixInput(args.input)
154
- let now
155
- if (autoTimestamp && fields.updated_at) {
156
- now = new Date()
157
- input.updated_at = now
160
+ if (autoTimestamp && fields[autoTimestamp.updatedAt]) {
161
+ const now = new Date()
162
+ input[autoTimestamp.updatedAt] = now
158
163
  }
159
164
  const criteria = computeCriteria(args)
160
165
 
@@ -340,9 +345,20 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
340
345
  acc[column.column_name].enum = column.column_type.match(/'(.+?)'/g).map(enumValue => enumValue.slice(1, enumValue.length - 1))
341
346
  }
342
347
 
343
- if (autoTimestamp && (column.column_name === 'updated_at' || column.column_name === 'inserted_at')) {
348
+ if (autoTimestamp && (column.column_name === autoTimestamp.createdAt || column.column_name === autoTimestamp.updatedAt)) {
344
349
  acc[column.column_name].autoTimestamp = true
345
350
  }
351
+
352
+ // To get generated information
353
+ /* istanbul ignore next */
354
+ if (db.isPg) {
355
+ acc[column.column_name].isGenerated = column.is_generated !== 'NEVER'
356
+ } else if (db.isSQLite) {
357
+ acc[column.column_name].isGenerated = column.is_generated === 'YES'
358
+ } else {
359
+ acc[column.column_name].isGenerated = column.is_generated.includes('GENERATED')
360
+ }
361
+
346
362
  return acc
347
363
  }, {})
348
364
 
@@ -363,6 +379,17 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
363
379
  const constraintsList = await queries.listConstraints(db, sql, table, schema)
364
380
  const primaryKeys = new Set()
365
381
 
382
+ /* istanbul ignore next */
383
+ function checkSQLitePrimaryKey (constraint) {
384
+ if (db.isSQLite) {
385
+ const validTypes = ['integer', 'uuid', 'serial']
386
+ const pkType = fields[constraint.column_name].sqlType.toLowerCase()
387
+ if (!validTypes.includes(pkType)) {
388
+ throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`)
389
+ }
390
+ }
391
+ }
392
+
366
393
  for (const constraint of constraintsList) {
367
394
  const field = fields[constraint.column_name]
368
395
 
@@ -378,23 +405,54 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
378
405
  if (constraint.constraint_type === 'PRIMARY KEY') {
379
406
  primaryKeys.add(constraint.column_name)
380
407
  // Check for SQLite typeless PK
381
- /* istanbul ignore next */
382
- if (db.isSQLite) {
383
- const validTypes = ['integer', 'uuid', 'serial']
384
- const pkType = fields[constraint.column_name].sqlType.toLowerCase()
385
- if (!validTypes.includes(pkType)) {
386
- throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`)
387
- }
388
- }
408
+ checkSQLitePrimaryKey(constraint)
389
409
  field.primaryKey = true
390
410
  }
391
411
 
392
412
  if (constraint.constraint_type === 'FOREIGN KEY') {
393
413
  field.foreignKey = true
414
+
415
+ // we need to ignore for coverage here becasue cannot be covered with sqlite (no schema support)
416
+ // istanbul ignore next
417
+ const foreignEntityName = singularize(camelcase(useSchemaInName ? camelcase(`${constraint.foreign_table_schema} ${constraint.foreign_table_name}`) : constraint.foreign_table_name))
418
+ // istanbul ignore next
419
+ const entityName = singularize(camelcase(useSchemaInName ? camelcase(`${constraint.table_schema} ${constraint.table_name}`) : constraint.table_name))
420
+ // istanbul ignore next
421
+ const loweredTableWithSchemaName = lowerCaseFirst(useSchemaInName ? camelcase(`${constraint.table_schema} ${camelcase(constraint.table_name)}`) : camelcase(constraint.table_name))
422
+ constraint.loweredTableWithSchemaName = loweredTableWithSchemaName
423
+ constraint.foreignEntityName = foreignEntityName
424
+ constraint.entityName = entityName
394
425
  currentRelations.push(constraint)
395
426
  }
396
427
  }
397
428
 
429
+ if (primaryKeys.size === 0) {
430
+ let found = false
431
+ for (const constraint of constraintsList) {
432
+ const field = fields[constraint.column_name]
433
+
434
+ /* istanbul ignore else */
435
+ if (constraint.constraint_type === 'UNIQUE') {
436
+ field.unique = true
437
+
438
+ /* istanbul ignore else */
439
+ if (!found) {
440
+ // Check for SQLite typeless PK
441
+ /* istanbul ignore next */
442
+ try {
443
+ checkSQLitePrimaryKey(constraint)
444
+ } catch {
445
+ continue
446
+ }
447
+
448
+ primaryKeys.add(constraint.column_name)
449
+ field.primaryKey = true
450
+ found = true
451
+ }
452
+ }
453
+ }
454
+ }
455
+
398
456
  const entity = createMapper(db, sql, log, table, fields, primaryKeys, currentRelations, queries, autoTimestamp, schema, useSchemaInName, limitConfig)
399
457
  entity.relations = currentRelations
400
458
 
@@ -23,7 +23,7 @@ async function listTables (db, sql, schemas) {
23
23
 
24
24
  async function listColumns (db, sql, table, schema) {
25
25
  const query = sql`
26
- SELECT column_name as column_name, data_type as udt_name, is_nullable as is_nullable, column_type as column_type
26
+ SELECT column_name as column_name, data_type as udt_name, is_nullable as is_nullable, column_type as column_type, extra as is_generated
27
27
  FROM information_schema.columns
28
28
  WHERE table_name = ${table}
29
29
  AND table_schema = ${schema}
@@ -33,7 +33,7 @@ async function listColumns (db, sql, table, schema) {
33
33
 
34
34
  async function listConstraints (db, sql, table, schema) {
35
35
  const query = sql`
36
- SELECT TABLE_NAME as table_name, COLUMN_NAME as column_name, CONSTRAINT_TYPE as constraint_type, referenced_table_name AS foreign_table_name, referenced_column_name AS foreign_column_name
36
+ SELECT TABLE_NAME as table_name, TABLE_SCHEMA as table_schema, COLUMN_NAME as column_name, CONSTRAINT_TYPE as constraint_type, referenced_table_name AS foreign_table_name, referenced_table_schema AS foreign_table_schema, referenced_column_name AS foreign_column_name
37
37
  FROM information_schema.table_constraints t
38
38
  JOIN information_schema.key_column_usage k
39
39
  USING (constraint_name, table_schema, table_name)
package/lib/queries/pg.js CHANGED
@@ -44,7 +44,7 @@ module.exports.listTables = listTables
44
44
 
45
45
  async function listColumns (db, sql, table, schema) {
46
46
  return db.query(sql`
47
- SELECT column_name, udt_name, is_nullable
47
+ SELECT column_name, udt_name, is_nullable, is_generated
48
48
  FROM information_schema.columns
49
49
  WHERE table_name = ${table}
50
50
  AND table_schema = ${schema}
@@ -55,7 +55,7 @@ module.exports.listColumns = listColumns
55
55
 
56
56
  async function listConstraints (db, sql, table, schema) {
57
57
  const query = sql`
58
- SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name
58
+ SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name, usage2.table_schema AS foreign_table_schema
59
59
  FROM information_schema.table_constraints constraints
60
60
  JOIN information_schema.key_column_usage usage
61
61
  ON constraints.constraint_name = usage.constraint_name
@@ -15,8 +15,10 @@ async function listTables (db, sql) {
15
15
  module.exports.listTables = listTables
16
16
 
17
17
  async function listColumns (db, sql, table) {
18
+ // pragma_table_info is not returning hidden column which tells if the column is generated or not
19
+ // therefore it is changed to pragma_table_xinfo
18
20
  const columns = await db.query(sql`
19
- SELECT * FROM pragma_table_info(${table})
21
+ SELECT * FROM pragma_table_xinfo(${table})
20
22
  `)
21
23
  for (const column of columns) {
22
24
  column.column_name = column.name
@@ -24,6 +26,8 @@ async function listColumns (db, sql, table) {
24
26
  column.udt_name = column.type.replace(/^([^(]+).*/, '$1').toLowerCase()
25
27
  // convert is_nullable
26
28
  column.is_nullable = column.notnull === 0 && column.pk === 0 ? 'YES' : 'NO'
29
+ // convert hidden to is_generated
30
+ column.is_generated = (column.hidden === 2 || column.hidden === 3) ? 'YES' : 'NO'
27
31
  }
28
32
  return columns
29
33
  }
@@ -45,6 +49,22 @@ async function listConstraints (db, sql, table) {
45
49
  })
46
50
  }
47
51
 
52
+ const indexes = await db.query(sql`
53
+ SELECT *
54
+ FROM pragma_index_list(${table}) as il
55
+ JOIN pragma_index_info(il.name) as ii
56
+ `)
57
+
58
+ for (const index of indexes) {
59
+ /* istanbul ignore else */
60
+ if (index.unique === 1) {
61
+ constraints.push({
62
+ column_name: index.name,
63
+ constraint_type: 'UNIQUE'
64
+ })
65
+ }
66
+ }
67
+
48
68
  const foreignKeys = await db.query(sql`
49
69
  SELECT *
50
70
  FROM pragma_foreign_key_list(${table})
package/mapper.js CHANGED
@@ -41,7 +41,15 @@ async function buildConnection (log, createConnectionPool, connectionString, poo
41
41
  return db
42
42
  }
43
43
 
44
+ const defaultAutoTimestampFields = {
45
+ createdAt: 'created_at',
46
+ updatedAt: 'updated_at'
47
+ }
48
+
44
49
  async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {} }) {
50
+ if (typeof autoTimestamp === 'boolean' && autoTimestamp === true) {
51
+ autoTimestamp = defaultAutoTimestampFields
52
+ }
45
53
  // TODO validate config using the schema
46
54
  if (!connectionString) {
47
55
  throw new Error('connectionString is required')
@@ -117,7 +125,8 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
117
125
  const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit)
118
126
  // Check for primary key of all entities
119
127
  if (entity.primaryKeys.size === 0) {
120
- throw Error(`Cannot find any primary keys for ${entity.name} entity`)
128
+ log.warn({ table }, 'Cannot find any primary keys for table')
129
+ continue
121
130
  }
122
131
 
123
132
  entities[entity.singularName] = entity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-mapper",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "A data mapper utility for SQL databases",
5
5
  "main": "mapper.js",
6
6
  "repository": {
@@ -14,19 +14,19 @@
14
14
  },
15
15
  "homepage": "https://github.com/platformatic/platformatic#readme",
16
16
  "devDependencies": {
17
- "fastify": "^4.5.3",
17
+ "fastify": "^4.10.2",
18
18
  "snazzy": "^9.0.0",
19
19
  "standard": "^17.0.0",
20
- "tap": "^16.0.0",
20
+ "tap": "^16.3.2",
21
21
  "tsd": "^0.25.0"
22
22
  },
23
23
  "dependencies": {
24
- "@databases/mysql": "^5.2.0",
25
- "@databases/pg": "^5.3.0",
24
+ "@databases/mysql": "^5.2.1",
25
+ "@databases/pg": "^5.4.1",
26
26
  "@databases/sql": "^3.2.0",
27
27
  "@databases/sqlite": "^4.0.2",
28
- "camelcase": "^6.0.0",
29
- "fastify-plugin": "^4.1.0",
28
+ "camelcase": "^6.3.0",
29
+ "fastify-plugin": "^4.4.0",
30
30
  "inflected": "^2.1.0"
31
31
  },
32
32
  "tsd": {
@@ -717,3 +717,155 @@ test('JSON type', { skip: !(isPg || isMysql8) }, async ({ teardown, same, equal,
717
717
  }
718
718
  }), [{ id: 2, config: { foo: 'bar', bar: 'foo' } }])
719
719
  })
720
+
721
+ test('stored and virtual generated columns should return for SQLite', { skip: !(isSQLite) }, async ({ teardown, same }) => {
722
+ async function onDatabaseLoad (db, sql) {
723
+ await clear(db, sql)
724
+ teardown(() => db.dispose())
725
+
726
+ await db.query(sql`CREATE TABLE generated_test (
727
+ id INTEGER PRIMARY KEY,
728
+ test INTEGER,
729
+ test_stored INTEGER GENERATED ALWAYS AS (test*2) STORED,
730
+ test_virtual INTEGER GENERATED ALWAYS AS (test*4) VIRTUAL
731
+ );`)
732
+ }
733
+
734
+ const mapper = await connect({
735
+ connectionString: connInfo.connectionString,
736
+ log: fakeLogger,
737
+ onDatabaseLoad,
738
+ ignore: {},
739
+ hooks: {}
740
+ })
741
+
742
+ const generatedTest = mapper.entities.generatedTest
743
+
744
+ // save - new record
745
+ same(await generatedTest.save({
746
+ input: { test: 1 }
747
+ }), { id: 1, test: 1, testStored: 2, testVirtual: 4 })
748
+
749
+ // save - update
750
+ same(await generatedTest.save({
751
+ input: { id: 1, test: 2 }
752
+ }), { id: 1, test: 2, testStored: 4, testVirtual: 8 })
753
+
754
+ // insert
755
+ same(await generatedTest.insert({
756
+ inputs: [{ test: 4 }]
757
+ }), [{ id: 2, test: 4, testStored: 8, testVirtual: 16 }])
758
+
759
+ // updateMany
760
+ same(await generatedTest.updateMany({
761
+ where: {
762
+ id: {
763
+ eq: 2
764
+ }
765
+ },
766
+ input: {
767
+ test: 8
768
+ }
769
+ }), [{ id: 2, test: 8, testStored: 16, testVirtual: 32 }])
770
+ })
771
+
772
+ test('stored generated columns should return for pg', { skip: !(isPg) }, async ({ teardown, same }) => {
773
+ async function onDatabaseLoad (db, sql) {
774
+ await clear(db, sql)
775
+ teardown(() => db.dispose())
776
+
777
+ await db.query(sql`CREATE TABLE generated_test (
778
+ id SERIAL PRIMARY KEY,
779
+ test INTEGER,
780
+ test_stored INTEGER GENERATED ALWAYS AS (test*2) STORED
781
+ );`)
782
+ }
783
+
784
+ const mapper = await connect({
785
+ connectionString: connInfo.connectionString,
786
+ log: fakeLogger,
787
+ onDatabaseLoad,
788
+ ignore: {},
789
+ hooks: {}
790
+ })
791
+
792
+ const generatedTest = mapper.entities.generatedTest
793
+
794
+ // save - new record
795
+ same(await generatedTest.save({
796
+ input: { test: 1 }
797
+ }), { id: 1, test: 1, testStored: 2 })
798
+
799
+ // save - update
800
+ same(await generatedTest.save({
801
+ input: { id: 1, test: 2 }
802
+ }), { id: 1, test: 2, testStored: 4 })
803
+
804
+ // insert
805
+ same(await generatedTest.insert({
806
+ inputs: [{ test: 4 }]
807
+ }), [{ id: 2, test: 4, testStored: 8 }])
808
+
809
+ // updateMany
810
+ same(await generatedTest.updateMany({
811
+ where: {
812
+ id: {
813
+ eq: 2
814
+ }
815
+ },
816
+ input: {
817
+ test: 8
818
+ }
819
+ }), [{ id: 2, test: 8, testStored: 16 }])
820
+ })
821
+
822
+ test('stored and virtual generated columns should return for pg', { skip: (isPg || isSQLite) }, async ({ teardown, same }) => {
823
+ async function onDatabaseLoad (db, sql) {
824
+ await clear(db, sql)
825
+ teardown(() => db.dispose())
826
+
827
+ await db.query(sql`CREATE TABLE generated_test (
828
+ id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
829
+ test INTEGER,
830
+ test_stored INTEGER GENERATED ALWAYS AS (test*2) STORED,
831
+ test_virtual INTEGER GENERATED ALWAYS AS (test*4) VIRTUAL
832
+ );`)
833
+ }
834
+
835
+ const mapper = await connect({
836
+ connectionString: connInfo.connectionString,
837
+ log: fakeLogger,
838
+ onDatabaseLoad,
839
+ ignore: {},
840
+ hooks: {}
841
+ })
842
+
843
+ const generatedTest = mapper.entities.generatedTest
844
+
845
+ // save - new record
846
+ same(await generatedTest.save({
847
+ input: { test: 1 }
848
+ }), { id: 1, test: 1, testStored: 2, testVirtual: 4 })
849
+
850
+ // save - update
851
+ same(await generatedTest.save({
852
+ input: { id: 1, test: 2 }
853
+ }), { id: 1, test: 2, testStored: 4, testVirtual: 8 })
854
+
855
+ // insert
856
+ same(await generatedTest.insert({
857
+ inputs: [{ test: 4 }]
858
+ }), [{ id: 2, test: 4, testStored: 8, testVirtual: 16 }])
859
+
860
+ // updateMany
861
+ same(await generatedTest.updateMany({
862
+ where: {
863
+ id: {
864
+ eq: 2
865
+ }
866
+ },
867
+ input: {
868
+ test: 8
869
+ }
870
+ }), [{ id: 2, test: 8, testStored: 16, testVirtual: 32 }])
871
+ })
package/test/helper.js CHANGED
@@ -4,7 +4,12 @@
4
4
  // See https://node-postgres.com/features/types/
5
5
  process.env.TZ = 'UTC'
6
6
 
7
- const connInfo = {}
7
+ const connInfo = {
8
+ autoTimestamp: {
9
+ createdAt: 'inserted_at',
10
+ updatedAt: 'updated_at'
11
+ }
12
+ }
8
13
 
9
14
  if (!process.env.DB || process.env.DB === 'postgresql') {
10
15
  connInfo.connectionString = 'postgres://postgres:postgres@127.0.0.1/postgres'
@@ -89,4 +94,9 @@ module.exports.clear = async function (db, sql) {
89
94
  await db.query(sql`DROP TABLE test2.pages`)
90
95
  } catch (err) {
91
96
  }
97
+
98
+ try {
99
+ await db.query(sql`DROP TABLE generated_test`)
100
+ } catch (err) {
101
+ }
92
102
  }
@@ -193,20 +193,3 @@ test('missing connectionString', async ({ rejects }) => {
193
193
 
194
194
  await rejects(app.ready(), /connectionString/)
195
195
  })
196
-
197
- test('throw if no primary keys', async ({ rejects, teardown }) => {
198
- async function onDatabaseLoad (db, sql) {
199
- await clear(db, sql)
200
-
201
- await db.query(sql`CREATE TABLE pages (
202
- title VARCHAR(255) NOT NULL
203
- );`)
204
- }
205
- await rejects(connect({
206
- connectionString: connInfo.connectionString,
207
- log: fakeLogger,
208
- onDatabaseLoad,
209
- ignore: {},
210
- hooks: {}
211
- }))
212
- })
@@ -0,0 +1,79 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+
5
+ const { clear, connInfo, isMysql8 } = require('./helper')
6
+ const { connect } = require('..')
7
+ const fakeLogger = {
8
+ trace: () => {},
9
+ warn: () => {},
10
+ error: () => {}
11
+ }
12
+
13
+ test('unique key', async ({ equal, not, same, teardown }) => {
14
+ async function onDatabaseLoad (db, sql) {
15
+ await clear(db, sql)
16
+ teardown(() => db.dispose())
17
+
18
+ const table = sql`
19
+ CREATE TABLE pages (
20
+ xx INTEGER DEFAULT NULL UNIQUE,
21
+ name varchar(75) DEFAULT NULL UNIQUE
22
+ );
23
+ `
24
+
25
+ await db.query(table)
26
+ }
27
+ const mapper = await connect({
28
+ connectionString: connInfo.connectionString,
29
+ log: fakeLogger,
30
+ onDatabaseLoad,
31
+ ignore: {},
32
+ hooks: {}
33
+ })
34
+ const pageEntity = mapper.entities.page
35
+ not(pageEntity, undefined)
36
+ equal(pageEntity.name, 'Page')
37
+ equal(pageEntity.singularName, 'page')
38
+ equal(pageEntity.pluralName, 'pages')
39
+ if (isMysql8) {
40
+ same(pageEntity.primaryKeys, new Set(['name']))
41
+ equal(pageEntity.camelCasedFields.name.primaryKey, true)
42
+ } else {
43
+ same(pageEntity.primaryKeys, new Set(['xx']))
44
+ equal(pageEntity.camelCasedFields.xx.primaryKey, true)
45
+ }
46
+ equal(pageEntity.camelCasedFields.xx.unique, true)
47
+ equal(pageEntity.camelCasedFields.name.unique, true)
48
+ })
49
+
50
+ test('no key', async ({ same, teardown, pass, equal, plan }) => {
51
+ plan(3)
52
+ async function onDatabaseLoad (db, sql) {
53
+ await clear(db, sql)
54
+ teardown(() => db.dispose())
55
+
56
+ const table = sql`
57
+ CREATE TABLE pages (
58
+ xx INTEGER DEFAULT NULL,
59
+ name varchar(75) DEFAULT NULL
60
+ );
61
+ `
62
+
63
+ await db.query(table)
64
+ }
65
+ const log = {
66
+ trace: () => {},
67
+ warn: (obj, str) => {
68
+ same(obj, { table: 'pages' })
69
+ equal(str, 'Cannot find any primary keys for table')
70
+ },
71
+ error: () => {}
72
+ }
73
+ const mapper = await connect({
74
+ connectionString: connInfo.connectionString,
75
+ log,
76
+ onDatabaseLoad
77
+ })
78
+ same(mapper.entities, {})
79
+ })
@@ -174,7 +174,6 @@ test('[pg] if schema is empty array, should find entities only in default \'publ
174
174
  hooks: {},
175
175
  schema: []
176
176
  })
177
-
178
177
  equal(Object.keys(mapper.entities).length, 1)
179
178
  const pageEntity = mapper.entities.page
180
179
  equal(pageEntity.name, 'Page')
@@ -318,3 +317,65 @@ test('addEntityHooks in entities with schema', { skip: isSQLite }, async ({ pass
318
317
  await entity.insert({ inputs: [{ title: 'hello' }, { title: 'world' }], fields: ['id', 'title'] })
319
318
  end()
320
319
  })
320
+
321
+ test('uses tables from different schemas with FK', { skip: isSQLite }, async ({ pass, teardown, equal }) => {
322
+ async function onDatabaseLoad (db, sql) {
323
+ await clear(db, sql)
324
+ teardown(() => db.dispose())
325
+
326
+ await db.query(sql`CREATE SCHEMA IF NOT EXISTS test1;`)
327
+ if (isMysql || isMysql8) {
328
+ await db.query(sql`CREATE TABLE IF NOT EXISTS \`test1\`.\`pages\` (
329
+ id SERIAL PRIMARY KEY,
330
+ title VARCHAR(255) NOT NULL
331
+ );`)
332
+ } else {
333
+ await db.query(sql`CREATE TABLE IF NOT EXISTS "test1"."pages" (
334
+ id SERIAL PRIMARY KEY,
335
+ title VARCHAR(255) NOT NULL
336
+ );`)
337
+ }
338
+
339
+ await db.query(sql`CREATE SCHEMA IF NOT EXISTS test2;`)
340
+
341
+ if (isMysql || isMysql8) {
342
+ await db.query(sql`CREATE TABLE IF NOT EXISTS \`test2\`.\`users\` (
343
+ id SERIAL PRIMARY KEY,
344
+ username VARCHAR(255) NOT NULL,
345
+ page_id BIGINT UNSIGNED,
346
+ FOREIGN KEY(page_id) REFERENCES test1.pages(id) ON DELETE CASCADE
347
+ );`)
348
+ } else {
349
+ await db.query(sql`CREATE TABLE IF NOT EXISTS "test2"."users" (
350
+ id SERIAL PRIMARY KEY,
351
+ username VARCHAR(255) NOT NULL,
352
+ page_id integer REFERENCES test1.pages(id)
353
+ );`)
354
+ }
355
+ }
356
+ const mapper = await connect({
357
+ connectionString: connInfo.connectionString,
358
+ log: fakeLogger,
359
+ onDatabaseLoad,
360
+ ignore: {},
361
+ hooks: {},
362
+ schema: ['test1', 'test2']
363
+ })
364
+ const pageEntity = mapper.entities.test1Page
365
+ equal(pageEntity.name, 'Test1Page')
366
+ equal(pageEntity.singularName, 'test1Page')
367
+ equal(pageEntity.pluralName, 'test1Pages')
368
+ equal(pageEntity.schema, 'test1')
369
+ equal(pageEntity.relations.length, 0)
370
+
371
+ const userEntity = mapper.entities.test2User
372
+ equal(userEntity.name, 'Test2User')
373
+ equal(userEntity.singularName, 'test2User')
374
+ equal(userEntity.pluralName, 'test2Users')
375
+ equal(userEntity.schema, 'test2')
376
+ equal(userEntity.relations.length, 1)
377
+ const userRelation = userEntity.relations[0]
378
+ equal(userRelation.foreignEntityName, 'test1Page')
379
+ equal(userRelation.entityName, 'test2User')
380
+ pass()
381
+ })
@@ -248,7 +248,7 @@ test('updateMany successful and update updated_at', async ({ pass, teardown, sam
248
248
  title VARCHAR(42),
249
249
  long_text TEXT,
250
250
  counter INTEGER,
251
- inserted_at TIMESTAMP,
251
+ created_at TIMESTAMP,
252
252
  updated_at TIMESTAMP
253
253
  );`)
254
254
  } else if (isMysql) {
@@ -257,7 +257,7 @@ test('updateMany successful and update updated_at', async ({ pass, teardown, sam
257
257
  title VARCHAR(42),
258
258
  long_text TEXT,
259
259
  counter INTEGER,
260
- inserted_at TIMESTAMP NULL DEFAULT NULL,
260
+ created_at TIMESTAMP NULL DEFAULT NULL,
261
261
  updated_at TIMESTAMP NULL DEFAULT NULL
262
262
  );`)
263
263
  } else {
@@ -266,7 +266,7 @@ test('updateMany successful and update updated_at', async ({ pass, teardown, sam
266
266
  title VARCHAR(42),
267
267
  long_text TEXT,
268
268
  counter INTEGER,
269
- inserted_at TIMESTAMP,
269
+ created_at TIMESTAMP,
270
270
  updated_at TIMESTAMP
271
271
  );`)
272
272
  }
@@ -313,6 +313,6 @@ test('updateMany successful and update updated_at', async ({ pass, teardown, sam
313
313
 
314
314
  const updatedPost3 = (await entity.find({ where: { id: { eq: '3' } } }))[0]
315
315
  same(updatedPost3.title, 'Updated title')
316
- same(createdPost3.insertedAt, updatedPost3.insertedAt)
316
+ same(createdPost3.createdAt, updatedPost3.createdAt)
317
317
  notSame(createdPost3.updatedAt, updatedPost3.updatedAt)
318
318
  })