@platformatic/sql-mapper 0.13.0 → 0.14.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
@@ -330,7 +330,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
330
330
  }
331
331
  }
332
332
 
333
- async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSchemaInName, ignore, limitConfig) {
333
+ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSchemaInName, ignore, limitConfig, schemaList) {
334
334
  // Compute the columns
335
335
  const columns = (await queries.listColumns(db, sql, table, schema)).filter((c) => !ignore[c.column_name])
336
336
  const fields = columns.reduce((acc, column) => {
@@ -409,15 +409,14 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
409
409
  field.primaryKey = true
410
410
  }
411
411
 
412
- if (constraint.constraint_type === 'FOREIGN KEY') {
412
+ // we need to ignore for coverage here because cannot be covered with sqlite (no schema support)
413
+ // istanbul ignore next
414
+ const isForeignKeySchemaInConfig = schemaList?.length > 0 ? schemaList.includes(constraint.foreign_table_schema) : true
415
+ /* istanbul ignore if */
416
+ if (constraint.constraint_type === 'FOREIGN KEY' && isForeignKeySchemaInConfig) {
413
417
  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
418
  const foreignEntityName = singularize(camelcase(useSchemaInName ? camelcase(`${constraint.foreign_table_schema} ${constraint.foreign_table_name}`) : constraint.foreign_table_name))
418
- // istanbul ignore next
419
419
  const entityName = singularize(camelcase(useSchemaInName ? camelcase(`${constraint.table_schema} ${constraint.table_name}`) : constraint.table_name))
420
- // istanbul ignore next
421
420
  const loweredTableWithSchemaName = lowerCaseFirst(useSchemaInName ? camelcase(`${constraint.table_schema} ${camelcase(constraint.table_name)}`) : camelcase(constraint.table_name))
422
421
  constraint.loweredTableWithSchemaName = loweredTableWithSchemaName
423
422
  constraint.foreignEntityName = foreignEntityName
@@ -85,67 +85,73 @@ async function listConstraints (db, sql, table) {
85
85
  module.exports.listConstraints = listConstraints
86
86
 
87
87
  async function insertOne (db, sql, table, schema, input, primaryKeys, fieldsToRetrieve) {
88
- const fieldNames = Object.keys(input)
89
- const keysToSql = fieldNames.map((key) => sql.ident(key))
90
- const valuesToSql = fieldNames.map((key) => sql.value(input[key]))
91
-
92
88
  const primaryKeyValues = {}
93
- let useUUID = false
94
- const where = []
95
- let autoIncrement = 0
89
+
90
+ let hasAutoIncrementPK = false
91
+
96
92
  for (const { key, sqlType } of primaryKeys) {
97
- keysToSql.push(sql.ident(key))
98
- // TODO figure out while this is not covered by tests
93
+ let primaryKeyValue = input[key]
94
+
99
95
  /* istanbul ignore next */
100
- if (sqlType === 'uuid') {
101
- useUUID = true
102
- primaryKeyValues[key] = randomUUID()
103
- } else if (autoIncrement > 1) {
104
- throw new Error('SQLite only supports autoIncrement on one column')
105
- } else if (input[key]) {
106
- primaryKeyValues[key] = input[key]
107
- } else {
108
- autoIncrement++
109
- primaryKeyValues[key] = null
96
+ if (primaryKeyValue === undefined) {
97
+ if (sqlType === 'uuid') {
98
+ primaryKeyValue = randomUUID()
99
+ } else if (!hasAutoIncrementPK) {
100
+ primaryKeyValue = null
101
+ hasAutoIncrementPK = true
102
+ } else {
103
+ throw new Error('SQLite only supports autoIncrement on one column')
104
+ }
105
+ input[key] = primaryKeyValue
110
106
  }
111
- valuesToSql.push(sql.value(primaryKeyValues[key]))
107
+
108
+ primaryKeyValues[key] = primaryKeyValue
112
109
  }
113
110
 
114
- const keys = sql.join(
115
- keysToSql,
116
- sql`, `
117
- )
111
+ const insertedKeys = []
112
+ const insertedValues = []
118
113
 
119
- const values = sql.join(
120
- valuesToSql,
121
- sql`, `
122
- )
114
+ for (const [key, value] of Object.entries(input)) {
115
+ insertedKeys.push(sql.ident(key))
116
+ insertedValues.push(sql.value(value))
117
+ }
123
118
 
124
- const insert = sql`
125
- INSERT INTO ${sql.ident(table)} (${keys})
126
- VALUES(${values})
119
+ const insertRawQuery = sql`
120
+ INSERT INTO ${sql.ident(table)} (${sql.join(insertedKeys, sql`, `)})
121
+ VALUES(${sql.join(insertedValues, sql`, `)})
127
122
  `
128
- await db.query(insert)
129
-
130
- if (!useUUID && primaryKeys.length === 1) {
131
- const res2 = await db.query(sql`
132
- SELECT last_insert_rowid()
133
- `)
134
-
135
- primaryKeyValues[primaryKeys[0].key] = res2[0]['last_insert_rowid()']
136
- }
137
123
 
138
- for (const { key } of primaryKeys) {
139
- where.push(sql`${sql.ident(key)} = ${sql.value(primaryKeyValues[key])}`)
124
+ if (fieldsToRetrieve.length === 0) {
125
+ await db.query(insertRawQuery)
126
+ return {}
127
+ } else {
128
+ return db.tx(async (transaction) => {
129
+ await transaction.query(insertRawQuery)
130
+
131
+ let selectInsertedRawQuery = null
132
+ if (hasAutoIncrementPK) {
133
+ selectInsertedRawQuery = sql`
134
+ SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
135
+ FROM ${sql.ident(table)}
136
+ WHERE _rowid_ = last_insert_rowid()
137
+ `
138
+ } else {
139
+ const where = []
140
+ for (const [key, value] of Object.entries(primaryKeyValues)) {
141
+ where.push(sql`${sql.ident(key)} = ${sql.value(value)}`)
142
+ }
143
+
144
+ selectInsertedRawQuery = sql`
145
+ SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
146
+ FROM ${sql.ident(table)}
147
+ WHERE ${sql.join(where, sql` AND `)}
148
+ `
149
+ }
150
+
151
+ const [insertedRaw] = await transaction.query(selectInsertedRawQuery)
152
+ return insertedRaw
153
+ })
140
154
  }
141
-
142
- const res = await db.query(sql`
143
- SELECT ${sql.join(fieldsToRetrieve, sql`, `)}
144
- FROM ${sql.ident(table)}
145
- WHERE ${sql.join(where, sql` AND `)}
146
- `)
147
-
148
- return res[0]
149
155
  }
150
156
 
151
157
  module.exports.insertOne = insertOne
package/mapper.js CHANGED
@@ -122,7 +122,7 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
122
122
  if (ignore[table] === true) {
123
123
  continue
124
124
  }
125
- const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit)
125
+ const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit, schemaList)
126
126
  // Check for primary key of all entities
127
127
  if (entity.primaryKeys.size === 0) {
128
128
  log.warn({ table }, 'Cannot find any primary keys for table')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-mapper",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "A data mapper utility for SQL databases",
5
5
  "main": "mapper.js",
6
6
  "repository": {
@@ -43,7 +43,7 @@ test('entity fields', async ({ equal, not, same, teardown }) => {
43
43
  equal(pageEntity.camelCasedFields.id.primaryKey, true)
44
44
  })
45
45
 
46
- test('entity API', { only: true }, async ({ equal, same, teardown, rejects }) => {
46
+ test('entity API', async ({ equal, same, teardown, rejects }) => {
47
47
  async function onDatabaseLoad (db, sql) {
48
48
  await clear(db, sql)
49
49
  teardown(() => db.dispose())
@@ -168,7 +168,7 @@ test('empty save', async ({ equal, same, teardown, rejects }) => {
168
168
  same(insertResult, { id: '1', theTitle: null })
169
169
  })
170
170
 
171
- test('insert with explicit PK value', async ({ same, teardown }) => {
171
+ test('insert with explicit integer PK value', async ({ same, teardown }) => {
172
172
  async function onDatabaseLoad (db, sql) {
173
173
  await clear(db, sql)
174
174
  teardown(() => db.dispose())
@@ -195,6 +195,108 @@ test('insert with explicit PK value', async ({ same, teardown }) => {
195
195
  })
196
196
  })
197
197
 
198
+ test('insert with explicit uuid PK value', { skip: !isSQLite }, async ({ same, teardown }) => {
199
+ async function onDatabaseLoad (db, sql) {
200
+ await clear(db, sql)
201
+ teardown(() => db.dispose())
202
+ await db.query(sql`CREATE TABLE pages (
203
+ id uuid PRIMARY KEY,
204
+ title varchar(255) NOT NULL
205
+ );`)
206
+ }
207
+ const mapper = await connect({
208
+ connectionString: connInfo.connectionString,
209
+ log: fakeLogger,
210
+ onDatabaseLoad,
211
+ ignore: {},
212
+ hooks: {}
213
+ })
214
+
215
+ const pageEntity = mapper.entities.page
216
+ const [newPage] = await pageEntity.insert({
217
+ fields: ['id', 'title'],
218
+ inputs: [{
219
+ id: '00000000-0000-0000-0000-000000000013',
220
+ title: '13th page with explicit id equal to 13'
221
+ }]
222
+ })
223
+ same(newPage, {
224
+ id: '00000000-0000-0000-0000-000000000013',
225
+ title: '13th page with explicit id equal to 13'
226
+ })
227
+ })
228
+
229
+ test('insert with explicit uuid PK value without rowid', { skip: !isSQLite }, async ({ same, teardown }) => {
230
+ async function onDatabaseLoad (db, sql) {
231
+ await clear(db, sql)
232
+ teardown(() => db.dispose())
233
+ await db.query(sql`CREATE TABLE pages (
234
+ id uuid PRIMARY KEY,
235
+ title varchar(255) NOT NULL
236
+ ) WITHOUT ROWID;`)
237
+ }
238
+ const mapper = await connect({
239
+ connectionString: connInfo.connectionString,
240
+ log: fakeLogger,
241
+ onDatabaseLoad,
242
+ ignore: {},
243
+ hooks: {}
244
+ })
245
+
246
+ const pageEntity = mapper.entities.page
247
+ const [newPage] = await pageEntity.insert({
248
+ fields: ['id', 'title'],
249
+ inputs: [{
250
+ id: '00000000-0000-0000-0000-000000000013',
251
+ title: '13th page with explicit id equal to 13'
252
+ }]
253
+ })
254
+ same(newPage, {
255
+ id: '00000000-0000-0000-0000-000000000013',
256
+ title: '13th page with explicit id equal to 13'
257
+ })
258
+ })
259
+
260
+ test('insert without fields to retrieve', { skip: !isSQLite }, async ({ same, teardown }) => {
261
+ async function onDatabaseLoad (db, sql) {
262
+ await clear(db, sql)
263
+ teardown(() => db.dispose())
264
+ await db.query(sql`CREATE TABLE pages (
265
+ id INTEGER PRIMARY KEY,
266
+ title varchar(255) NOT NULL
267
+ );`)
268
+ }
269
+ const mapper = await connect({
270
+ connectionString: connInfo.connectionString,
271
+ log: fakeLogger,
272
+ onDatabaseLoad,
273
+ ignore: {},
274
+ hooks: {}
275
+ })
276
+
277
+ const pageEntity = mapper.entities.page
278
+ await pageEntity.insert({
279
+ fields: [],
280
+ inputs: [{
281
+ id: '13',
282
+ title: '13th page with explicit id equal to 13'
283
+ }]
284
+ })
285
+
286
+ const [newPage] = await pageEntity.find({
287
+ where: {
288
+ id: {
289
+ eq: '13'
290
+ }
291
+ }
292
+ })
293
+
294
+ same(newPage, {
295
+ id: '13',
296
+ title: '13th page with explicit id equal to 13'
297
+ })
298
+ })
299
+
198
300
  test('[SQLite] - UUID', { skip: !isSQLite }, async ({ pass, teardown, same, equal }) => {
199
301
  const mapper = await connect({
200
302
  connectionString: connInfo.connectionString,