velocious 1.0.35 → 1.0.37

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.
Files changed (44) hide show
  1. package/README.md +1 -0
  2. package/package.json +5 -3
  3. package/peak_flow.yml +22 -2
  4. package/spec/cli/commands/db/create-spec.js +6 -0
  5. package/spec/database/drivers/mysql/connection-spec.js +1 -1
  6. package/spec/database/record/create-spec.js +24 -0
  7. package/spec/database/record/update-spec.js +15 -0
  8. package/spec/database/record/validations-spec.js +28 -0
  9. package/spec/database/transactions-spec.js +36 -0
  10. package/spec/dummy/src/config/{configuration.sqlite.js → configuration.peakflow.pgsql.js} +11 -38
  11. package/spec/dummy/src/models/task.js +1 -0
  12. package/src/cli/commands/db/create.js +15 -10
  13. package/src/cli/commands/test.js +8 -0
  14. package/src/configuration.js +14 -0
  15. package/src/database/drivers/base.js +21 -4
  16. package/src/database/drivers/mssql/index.js +12 -3
  17. package/src/database/drivers/mssql/sql/create-database.js +1 -1
  18. package/src/database/drivers/mysql/index.js +2 -2
  19. package/src/database/drivers/pgsql/column.js +10 -0
  20. package/src/database/drivers/pgsql/foreign-key.js +13 -0
  21. package/src/database/drivers/pgsql/index.js +172 -0
  22. package/src/database/drivers/pgsql/options.js +18 -0
  23. package/src/database/drivers/pgsql/query-parser.js +4 -0
  24. package/src/database/drivers/pgsql/sql/create-database.js +37 -0
  25. package/src/database/drivers/pgsql/sql/create-index.js +4 -0
  26. package/src/database/drivers/pgsql/sql/create-table.js +4 -0
  27. package/src/database/drivers/pgsql/sql/delete.js +19 -0
  28. package/src/database/drivers/pgsql/sql/drop-table.js +4 -0
  29. package/src/database/drivers/pgsql/sql/insert.js +4 -0
  30. package/src/database/drivers/pgsql/sql/update.js +31 -0
  31. package/src/database/drivers/pgsql/table.js +62 -0
  32. package/src/database/drivers/sqlite/base.js +4 -0
  33. package/src/database/drivers/sqlite/column.js +10 -0
  34. package/src/database/migrator.js +1 -1
  35. package/src/database/query/create-database-base.js +1 -1
  36. package/src/database/query/create-table-base.js +16 -2
  37. package/src/database/query/index.js +10 -1
  38. package/src/database/query/insert-base.js +1 -1
  39. package/src/database/record/index.js +162 -18
  40. package/src/database/record/validators/base.js +2 -0
  41. package/src/database/record/validators/presence.js +13 -0
  42. package/src/database/record/validators/uniqueness.js +23 -0
  43. package/src/testing/test-runner.js +10 -0
  44. package/spec/dummy/src/config/configuration.mariadb.js +0 -97
@@ -0,0 +1,172 @@
1
+ import Base from "../base.js"
2
+ import {Client, escapeLiteral} from "pg"
3
+ import CreateDatabase from "./sql/create-database.js"
4
+ import CreateIndex from "./sql/create-index.js"
5
+ import CreateTable from "./sql/create-table.js"
6
+ import Delete from "./sql/delete.js"
7
+ import {digg} from "diggerize"
8
+ import DropTable from "./sql/drop-table.js"
9
+ import Insert from "./sql/insert.js"
10
+ import Options from "./options.js"
11
+ import QueryParser from "./query-parser.js"
12
+ import Table from "./table.js"
13
+ import Update from "./sql/update.js"
14
+
15
+ export default class VelociousDatabaseDriversPgsql extends Base{
16
+ async connect() {
17
+ const client = new Client(this.connectArgs())
18
+
19
+ try {
20
+ await client.connect()
21
+ } catch (error) {
22
+ // Re-throw to recover real stack trace
23
+ throw new Error(`Connect to Postgres server failed: ${error.message}`)
24
+ }
25
+
26
+ this.connection = client
27
+ }
28
+
29
+ disconnect() {
30
+ this.connection.end()
31
+ }
32
+
33
+ connectArgs() {
34
+ const args = this.getArgs()
35
+ const connectArgs = []
36
+ const forward = ["database", "host", "password", "port"]
37
+
38
+ for (const forwardValue of forward) {
39
+ if (forwardValue in args) connectArgs[forwardValue] = digg(args, forwardValue)
40
+ }
41
+
42
+ if ("username" in args) connectArgs["user"] = args["username"]
43
+
44
+ return connectArgs
45
+ }
46
+
47
+ async close() {
48
+ await this.connection.end()
49
+ this.connection = undefined
50
+ }
51
+
52
+ createDatabaseSql(databaseName, args) {
53
+ const createArgs = Object.assign({databaseName, driver: this}, args)
54
+ const createDatabase = new CreateDatabase(createArgs)
55
+
56
+ return createDatabase.toSql()
57
+ }
58
+
59
+ createIndexSql(indexData) {
60
+ const createArgs = Object.assign({driver: this}, indexData)
61
+ const createIndex = new CreateIndex(createArgs)
62
+
63
+ return createIndex.toSql()
64
+ }
65
+
66
+ createTableSql(tableData) {
67
+ const createArgs = Object.assign({tableData, driver: this, indexInCreateTable: false})
68
+ const createTable = new CreateTable(createArgs)
69
+
70
+ return createTable.toSql()
71
+ }
72
+
73
+ async disableForeignKeys() {
74
+ await this.query("SET FOREIGN_KEY_CHECKS = 0")
75
+ }
76
+
77
+ async enableForeignKeys() {
78
+ await this.query("SET FOREIGN_KEY_CHECKS = 1")
79
+ }
80
+
81
+ dropTableSql(tableName, args = {}) {
82
+ const dropArgs = Object.assign({tableName, driver: this}, args)
83
+ const dropTable = new DropTable(dropArgs)
84
+
85
+ return dropTable.toSql()
86
+ }
87
+
88
+ getType = () => "pgsql"
89
+ primaryKeyType = () => "bigint"
90
+
91
+ async query(sql) {
92
+ let response
93
+
94
+ try {
95
+ response = await this.connection.query(sql)
96
+ } catch (error) {
97
+ throw new Error(`Query failed: ${error.message} with SQL: ${sql}`)
98
+ }
99
+
100
+ return response.rows
101
+ }
102
+
103
+ queryToSql(query) {
104
+ return new QueryParser({query}).toSql()
105
+ }
106
+
107
+ shouldSetAutoIncrementWhenPrimaryKey = () => true
108
+
109
+ escape(value) {
110
+ if (!this.connection) throw new Error("Can't escape before connected")
111
+ if (typeof value === "number") return value
112
+
113
+ const escapedValueWithQuotes = this.connection.escapeLiteral(this._convertValue(value))
114
+
115
+ return escapedValueWithQuotes.slice(1, escapedValueWithQuotes.length - 1)
116
+ }
117
+
118
+ quote(value) {
119
+ if (!this.connection) throw new Error("Can't escape before connected")
120
+ if (typeof value === "number") return value
121
+
122
+ return this.connection.escapeLiteral(this._convertValue(value))
123
+ }
124
+
125
+ deleteSql({tableName, conditions}) {
126
+ const deleteInstruction = new Delete({conditions, driver: this, tableName})
127
+
128
+ return deleteInstruction.toSql()
129
+ }
130
+
131
+ insertSql(args) {
132
+ const insertArgs = Object.assign({driver: this}, args)
133
+ const insert = new Insert(insertArgs)
134
+
135
+ return insert.toSql()
136
+ }
137
+
138
+ async getTables() {
139
+ const result = await this.query("SELECT * FROM information_schema.tables WHERE table_catalog = CURRENT_DATABASE() AND table_schema = 'public'")
140
+ const tables = []
141
+
142
+ for (const row of result) {
143
+ const table = new Table(this, row)
144
+
145
+ tables.push(table)
146
+ }
147
+
148
+ return tables
149
+ }
150
+
151
+ async lastInsertID() {
152
+ const result = await this.query("SELECT LASTVAL() AS last_insert_id")
153
+
154
+ return digg(result, 0, "last_insert_id")
155
+ }
156
+
157
+ options() {
158
+ if (!this._options) this._options = new Options({driver: this})
159
+
160
+ return this._options
161
+ }
162
+
163
+ async startTransaction() {
164
+ return await this.query("START TRANSACTION")
165
+ }
166
+
167
+ updateSql({conditions, data, tableName}) {
168
+ const update = new Update({conditions, data, driver: this, tableName})
169
+
170
+ return update.toSql()
171
+ }
172
+ }
@@ -0,0 +1,18 @@
1
+ import QueryParserOptions from "../../query-parser/options.js"
2
+
3
+ export default class VelociousDatabaseDriversPgsqlOptions extends QueryParserOptions {
4
+ constructor(options) {
5
+ options.columnQuote = "\""
6
+ options.indexQuote = "\""
7
+ options.stringQuote = "'"
8
+ options.tableQuote = "\""
9
+
10
+ super(options)
11
+ }
12
+
13
+ quote(string) {
14
+ if (!this.driver) throw new Error("Driver not set")
15
+
16
+ return this.driver.quote(string)
17
+ }
18
+ }
@@ -0,0 +1,4 @@
1
+ import BaseQueryParser from "../../query-parser/base-query-parser.js"
2
+
3
+ export default class VelociousDatabaseConnectionDriversPgsqlQueryParser extends BaseQueryParser {
4
+ }
@@ -0,0 +1,37 @@
1
+ import CreateDatabaseBase from "../../../query/create-database-base.js"
2
+ import {digs} from "diggerize"
3
+
4
+ export default class VelociousDatabaseConnectionDriversPgsqlSqlCreateDatabase extends CreateDatabaseBase {
5
+ toSql() {
6
+ const {databaseName} = this
7
+ const options = this.getOptions()
8
+ const sqls = []
9
+
10
+ if (this.ifNotExists) {
11
+ sqls.push("CREATE EXTENSION IF NOT EXISTS dblink")
12
+
13
+ const connectArgs = this._driver.connectArgs()
14
+ const {password, user} = digs(connectArgs, "password", "user")
15
+ const port = connectArgs.port || 5432
16
+ const sql = `
17
+ DO
18
+ $do$
19
+ BEGIN
20
+ IF EXISTS (SELECT FROM ${options.quoteTableName("pg_database")} WHERE ${options.quoteColumnName("datname")} = ${options.quote(databaseName)}) THEN
21
+ RAISE NOTICE 'Database already exists'; -- optional
22
+ ELSE
23
+ PERFORM dblink_connect('host=localhost port=' || ${port} || ' user=' || ${options.quote(user)} || ' password=' || ${options.quote(password)} || ' dbname=' || current_database());
24
+ PERFORM dblink_exec('CREATE DATABASE ' || ${options.quote(databaseName)});
25
+ END IF;
26
+ END
27
+ $do$;
28
+ `
29
+
30
+ sqls.push(sql)
31
+ } else {
32
+ sqls.push(`CREATE DATABASE ${options.quoteDatabaseName(databaseName)}`)
33
+ }
34
+
35
+ return sqls
36
+ }
37
+ }
@@ -0,0 +1,4 @@
1
+ import CreateIndexBase from "../../../query/create-index-base.js"
2
+
3
+ export default class VelociousDatabaseConnectionDriversPgsqlSqlCreateIndex extends CreateIndexBase {
4
+ }
@@ -0,0 +1,4 @@
1
+ import CreateTableBase from "../../../query/create-table-base.js"
2
+
3
+ export default class VelociousDatabaseConnectionDriversPgsqlSqlCreateTable extends CreateTableBase {
4
+ }
@@ -0,0 +1,19 @@
1
+ import DeleteBase from "../../../query/delete-base.js"
2
+
3
+ export default class VelociousDatabaseConnectionDriversPgsqlSqlDelete extends DeleteBase {
4
+ toSql() {
5
+ let sql = `DELETE FROM ${this.getOptions().quoteTableName(this.tableName)} WHERE `
6
+ let count = 0
7
+
8
+ for (let columnName in this.conditions) {
9
+ if (count > 0) sql += " AND "
10
+
11
+ sql += this.getOptions().quoteColumnName(columnName)
12
+ sql += " = "
13
+ sql += this.getOptions().quote(this.conditions[columnName])
14
+ count++
15
+ }
16
+
17
+ return sql
18
+ }
19
+ }
@@ -0,0 +1,4 @@
1
+ import DropTableBase from "../../../query/drop-table-base.js"
2
+
3
+ export default class VelociousDatabaseConnectionDriversPgsqlSqlDropTable extends DropTableBase {
4
+ }
@@ -0,0 +1,4 @@
1
+ import InsertBase from "../../../query/insert-base.js"
2
+
3
+ export default class VelociousDatabaseConnectionDriversPgsqlSqlInsert extends InsertBase {
4
+ }
@@ -0,0 +1,31 @@
1
+ import UpdateBase from "../../../query/update-base.js"
2
+
3
+ export default class VelociousDatabaseConnectionDriversPgsqlSqlUpdate extends UpdateBase {
4
+ toSql() {
5
+ let sql = `UPDATE ${this.getOptions().quoteTableName(this.tableName)} SET `
6
+ let count = 0
7
+
8
+ for (let columnName in this.data) {
9
+ if (count > 0) sql += ", "
10
+
11
+ sql += this.getOptions().quoteColumnName(columnName)
12
+ sql += " = "
13
+ sql += this.getOptions().quote(this.data[columnName])
14
+ count++
15
+ }
16
+
17
+ sql += " WHERE "
18
+ count = 0
19
+
20
+ for (let columnName in this.conditions) {
21
+ if (count > 0) sql += " AND "
22
+
23
+ sql += this.getOptions().quoteColumnName(columnName)
24
+ sql += " = "
25
+ sql += this.getOptions().quote(this.conditions[columnName])
26
+ count++
27
+ }
28
+
29
+ return sql
30
+ }
31
+ }
@@ -0,0 +1,62 @@
1
+ import Column from "./column.js"
2
+ import ForeignKey from "./foreign-key.js"
3
+
4
+ export default class VelociousDatabaseDriversPgsqlTable {
5
+ constructor(driver, data) {
6
+ this.data = data
7
+ this.driver = driver
8
+ }
9
+
10
+ async getColumns() {
11
+ const result = await this.driver.query(`SELECT * FROM information_schema.columns WHERE table_catalog = CURRENT_DATABASE() AND table_schema = 'public' AND table_name = '${this.getName()}'`)
12
+ const columns = []
13
+
14
+ for (const data of result) {
15
+ const column = new Column(this, data)
16
+
17
+ columns.push(column)
18
+ }
19
+
20
+ return columns
21
+ }
22
+
23
+ async getForeignKeys() {
24
+ const sql = `
25
+ SELECT
26
+ tc.constraint_name,
27
+ tc.table_name,
28
+ kcu.column_name,
29
+ ccu.table_name AS foreign_table_name,
30
+ ccu.column_name AS foreign_column_name
31
+
32
+ FROM
33
+ information_schema.table_constraints AS tc
34
+
35
+ JOIN information_schema.key_column_usage AS kcu ON
36
+ tc.constraint_name = kcu.constraint_name
37
+
38
+ JOIN information_schema.constraint_column_usage AS ccu
39
+ ON ccu.constraint_name = tc.constraint_name
40
+
41
+ WHERE
42
+ constraint_type = 'FOREIGN KEY' AND
43
+ tc.table_catalog = CURRENT_DATABASE() AND
44
+ tc.table_name = ${this.driver.quote(this.getName())}
45
+ `
46
+
47
+ const foreignKeyRows = await this.driver.query(sql)
48
+ const foreignKeys = []
49
+
50
+ for (const foreignKeyRow of foreignKeyRows) {
51
+ const foreignKey = new ForeignKey(foreignKeyRow)
52
+
53
+ foreignKeys.push(foreignKey)
54
+ }
55
+
56
+ return foreignKeys
57
+ }
58
+
59
+ getName() {
60
+ return this.data.table_name
61
+ }
62
+ }
@@ -164,6 +164,8 @@ export default class VelociousDatabaseDriversSqliteBase extends Base {
164
164
  shouldSetAutoIncrementWhenPrimaryKey = () => false
165
165
 
166
166
  escape(value) {
167
+ value = this._convertValue(value)
168
+
167
169
  const type = typeof value
168
170
 
169
171
  if (type != "string") value = `${value}`
@@ -175,6 +177,8 @@ export default class VelociousDatabaseDriversSqliteBase extends Base {
175
177
  }
176
178
 
177
179
  quote(value) {
180
+ value = this._convertValue(value)
181
+
178
182
  const type = typeof value
179
183
 
180
184
  if (type == "number") return value
@@ -11,4 +11,14 @@ export default class VelociousDatabaseDriversSqliteColumn {
11
11
 
12
12
  return this.column.name
13
13
  }
14
+
15
+ getType() {
16
+ const match = this.column.type.match(/(.*)\((\d+)\)$/)
17
+
18
+ if (match) {
19
+ return match[1].toLowerCase()
20
+ }
21
+
22
+ return this.column.type.toLowerCase()
23
+ }
14
24
  }
@@ -214,7 +214,7 @@ export default class VelociousDatabaseMigrator {
214
214
  const dateString = digg(migration, "date")
215
215
  const existingSchemaMigrations = await db.newQuery()
216
216
  .from("schema_migrations")
217
- .where({version: dateString})
217
+ .where({version: `${dateString}`})
218
218
  .results()
219
219
 
220
220
  if (existingSchemaMigrations.length == 0) {
@@ -15,6 +15,6 @@ export default class VelociousDatabaseQueryCreateDatabaseBase extends QueryBase
15
15
 
16
16
  sql += ` ${this.getOptions().quoteDatabaseName(databaseName)}`
17
17
 
18
- return sql
18
+ return [sql]
19
19
  }
20
20
  }
@@ -37,6 +37,10 @@ export default class VelociousDatabaseQueryCreateTableBase extends QueryBase {
37
37
  let maxlength = column.getMaxLength()
38
38
  let type = column.getType().toUpperCase()
39
39
 
40
+ if (type == "DATETIME" && databaseType == "pgsql") {
41
+ type = "TIMESTAMP"
42
+ }
43
+
40
44
  if (type == "STRING") {
41
45
  type = "VARCHAR"
42
46
  maxlength ||= 255
@@ -50,6 +54,10 @@ export default class VelociousDatabaseQueryCreateTableBase extends QueryBase {
50
54
  type = "INTEGER"
51
55
  }
52
56
 
57
+ if (databaseType == "pgsql" && column.getAutoIncrement() && column.getPrimaryKey()) {
58
+ type = "SERIAL"
59
+ }
60
+
53
61
  if (columnCount > 1) sql += ", "
54
62
 
55
63
  sql += `${options.quoteColumnName(column.getName())} ${type}`
@@ -59,6 +67,12 @@ export default class VelociousDatabaseQueryCreateTableBase extends QueryBase {
59
67
  if (column.getAutoIncrement() && driver.shouldSetAutoIncrementWhenPrimaryKey()) {
60
68
  if (databaseType == "mssql") {
61
69
  sql += " IDENTITY"
70
+ } else if (databaseType == "pgsql") {
71
+ if (column.getAutoIncrement() && column.getPrimaryKey()) {
72
+ // Do nothing
73
+ } else {
74
+ throw new Error("pgsql auto increment must be primary key")
75
+ }
62
76
  } else {
63
77
  sql += " AUTO_INCREMENT"
64
78
  }
@@ -83,7 +97,7 @@ export default class VelociousDatabaseQueryCreateTableBase extends QueryBase {
83
97
  throw new Error(`Unknown foreign key type given: ${column.getForeignKey()} (${typeof column.getForeignKey()})`)
84
98
  }
85
99
 
86
- sql += ` REFERENCES ${driver.quoteTable(foreignKeyTable)}(${driver.quoteColumn(foreignKeyColumn)})`
100
+ sql += ` REFERENCES ${options.quoteTableName(foreignKeyTable)}(${options.quoteColumnName(foreignKeyColumn)})`
87
101
  }
88
102
  }
89
103
 
@@ -98,7 +112,7 @@ export default class VelociousDatabaseQueryCreateTableBase extends QueryBase {
98
112
  sql += " INDEX"
99
113
 
100
114
  if (index.getName()) {
101
- sql += ` ${index.getName()}`
115
+ sql += ` ${options.quoteIndexName(index.getName())}`
102
116
  }
103
117
 
104
118
  sql += " ("
@@ -48,10 +48,19 @@ export default class VelociousDatabaseQuery {
48
48
  }
49
49
 
50
50
  async count() {
51
+ // Generate count SQL
52
+ let sql = "COUNT(id)"
53
+
54
+ if (this.driver.getType() == "pgsql") sql += "::int"
55
+
56
+ sql += " AS count"
57
+
58
+
59
+ // Clone query and execute count
51
60
  const countQuery = this.clone()
52
61
 
53
62
  countQuery._selects = []
54
- countQuery.select("COUNT(id) AS count")
63
+ countQuery.select(sql)
55
64
 
56
65
  const results = await countQuery._executeQuery()
57
66
 
@@ -34,7 +34,7 @@ export default class VelociousDatabaseQueryInsertBase {
34
34
  if (Object.keys(this.data).length <= 0) {
35
35
  sql += lastInsertedSQL
36
36
  }
37
- } else if (driver.getType() == "mysql") {
37
+ } else if (driver.getType() == "mysql" || driver.getType() == "pgsql") {
38
38
  lastInsertedSQL = ` RETURNING ${driver.quoteColumn(this.returnLastInsertedColumnName)} AS lastInsertID`
39
39
  }
40
40
  }