Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cockroach Dialect #12346

Open
wants to merge 58 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
670487d
cockroach dialect
cpaczek Jan 7, 2022
cd2d2d7
Merge remote-tracking branch 'strapi/master' into cockroach-dialect
cpaczek Jan 29, 2022
ed9ec22
Update knex and begin to add cockroach dialect
cpaczek Jan 29, 2022
316f9e5
update knex to 1.0.1
cpaczek Jan 29, 2022
220bc14
Merge remote-tracking branch 'strapi/master' into cockroach-dialect
cpaczek Feb 3, 2022
fe7a1ad
Update to Knex 1.0.2 and implement alterType
cpaczek Feb 3, 2022
8a3b097
quickfix
cpaczek Feb 3, 2022
c517eef
more logging
cpaczek Feb 3, 2022
71a81a1
Merge remote-tracking branch 'strapi/master' into cockroach-dialect
cpaczek Apr 16, 2022
a357086
Catch up with master
cpaczek Apr 16, 2022
33980fe
Merge branch 'cockroach-dialect-2' into cockroach-dialect
cpaczek Apr 16, 2022
a93a287
alterNullable false
cpaczek Apr 16, 2022
9ede70a
CRDB Rowid migration issue bypass
cpaczek Apr 17, 2022
5ddc2c7
clean up
cpaczek Apr 18, 2022
a77938f
clean up builder.js
cpaczek Apr 18, 2022
dbd2b50
Merge remote-tracking branch 'strapi/master' into cockroach-dialect
cpaczek Jul 22, 2022
b1e210b
update knex
cpaczek Jul 22, 2022
ea8ab6c
Merge remote-tracking branch 'origin/releases/4.5.6' into cockroach-d…
cpaczek Jan 17, 2023
6108c96
remove console.logs and make crdb working :)
cpaczek Jan 18, 2023
b242e41
Working version of CRDB
cpaczek Jan 19, 2023
aa38e43
Remove magic numbers and remove parseInt on order row incremeter
cpaczek Jan 19, 2023
d3dd310
remove old comment
cpaczek Jan 20, 2023
22a0a9a
revert back to main
cpaczek Jan 20, 2023
9c42580
remove eslint ignore
cpaczek Jan 20, 2023
cc844e9
Parse int needed for CRDB
cpaczek Jan 20, 2023
b4df4cb
fix ids not displaying in tables
cpaczek Jan 20, 2023
fc788e4
remove unneeded if check
cpaczek Jan 20, 2023
623ec80
remove console.log
cpaczek Jan 20, 2023
10cede9
add warning to startup log
cpaczek Jan 20, 2023
2c94477
remove console.log
cpaczek Jan 20, 2023
c0a7d9d
change crdb to use sql_sequence
cpaczek Jan 20, 2023
4a2db0e
fix typo
cpaczek Jan 20, 2023
ac2401b
remove unneeded case
cpaczek Jan 21, 2023
8406f49
remove duplicate code
cpaczek Jan 21, 2023
c5b0fd5
remove unneeded code
cpaczek Jan 21, 2023
8b55da5
change logs
cpaczek Jan 21, 2023
daaf3c0
remove weird spacing
cpaczek Jan 21, 2023
436fb04
Merge remote-tracking branch 'origin/main' into cockroach-dialect
cpaczek Jan 23, 2023
1b76ed5
Merge remote-tracking branch 'origin/releases/4.6.0-beta.2' into cock…
cpaczek Jan 23, 2023
05932dc
merge main into cockroach
cpaczek Feb 27, 2023
cc43b90
update lock
cpaczek Feb 27, 2023
92b32b6
fix lint and disable automatic json type parsing
cpaczek Feb 27, 2023
2147386
use main repo lock
cpaczek Feb 27, 2023
b35e143
Merge remote-tracking branch 'origin/main' into cockroach-dialect
cpaczek Mar 3, 2023
9b50deb
fix index column diff
cpaczek Mar 3, 2023
44e5954
add tests
cpaczek Mar 4, 2023
ac155cc
update image for ee tests
cpaczek Mar 4, 2023
96247d8
fix healthcheck
cpaczek Mar 4, 2023
ad36079
remove init script
cpaczek Mar 4, 2023
5e07dc1
change db to defaultdb
cpaczek Mar 4, 2023
72a2140
update actions
cpaczek Mar 4, 2023
f865b65
fix race condition
cpaczek Mar 4, 2023
d51f658
convert promise to for of loop
cpaczek Mar 4, 2023
66c3d73
convert to for of
cpaczek Mar 4, 2023
ef8eb79
fix relations
cpaczek Mar 5, 2023
1c1d8c0
remove transaction for select query
cpaczek Mar 5, 2023
a1556b3
add transacting back
cpaczek Mar 6, 2023
fcfc414
set isolation mode to read commited
cpaczek Mar 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/tests.yml
Expand Up @@ -136,6 +136,39 @@ jobs:
- uses: ./.github/actions/run-api-tests
with:
dbOptions: '--dbclient=postgres --dbhost=localhost --dbport=5432 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
api_ce_crdb:
runs-on: ubuntu-latest
needs: [lint, unit_back, unit_front]
name: '[CE] API Integration (cockroachdb, node: ${{ matrix.node }})'
strategy:
matrix:
node: [14, 16, 18]
services:
cockroachdb:
# Docker Hub image
image: timveil/cockroachdb-single-node:latest
# Set health checks to wait until cockroachdb has started
options: >-
--health-cmd "curl http://cockroachdb:8080/health?ready=1"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 26257:26257
- 8080:8080
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- uses: ./.github/actions/run-api-tests
with:
dbOptions: '--dbclient=cockroachdb --dbhost=localhost --dbport=26257 --dbname=defaultdb --dbusername=root --dbpassword=""'

api_ce_mysql:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -280,6 +313,43 @@ jobs:
with:
dbOptions: '--dbclient=postgres --dbhost=localhost --dbport=5432 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
runEE: true
api_ee_crdb:
runs-on: ubuntu-latest
needs: [lint, unit_back, unit_front]
name: '[EE] API Integration (cockroachdb, node: ${{ matrix.node }})'
if: github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')
env:
STRAPI_LICENSE: ${{ secrets.strapiLicense }}
strategy:
matrix:
node: [14, 16, 18]
services:
cockroachdb:
# Docker Hub image
image: timveil/cockroachdb-single-node:latest
# Set health checks to wait until cockroachdb has started
options: >-
--health-cmd "curl http://cockroachdb:8080/health?ready=1"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 26257:26257
- 8080:8080
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- uses: ./.github/actions/run-api-tests
with:
dbOptions: '--dbclient=cockroachdb --dbhost=localhost --dbport=26257 --dbname=defaultdb --dbusername=root --dbpassword=""'
runEE: true

api_ee_mysql:
runs-on: ubuntu-latest
Expand Down
69 changes: 69 additions & 0 deletions packages/core/database/lib/dialects/cockroachdb/index.js
@@ -0,0 +1,69 @@
'use strict';

const errors = require('../../errors');
const { Dialect } = require('../dialect');
const CockroachdbSchemaInspector = require('./schema-inspector');

class CockroachDialect extends Dialect {
constructor(db) {
super(db);

this.schemaInspector = new CockroachdbSchemaInspector(db);
}

useReturning() {
return true;
}

initialize() {
this.db.connection.client.driver.types.setTypeParser(
this.db.connection.client.driver.types.builtins.DATE,
'text',
(v) => v
); // Don't cast DATE string to Date()
this.db.connection.client.driver.types.setTypeParser(
this.db.connection.client.driver.types.builtins.NUMERIC,
'text',
parseFloat
);
// Don't parse JSONB automatically
this.db.connection.client.driver.types.setTypeParser(
this.db.connection.client.driver.types.builtins.JSONB,
'text',
(v) => v
);
// sets default int to 32 bit and sets serial normalization to sql_sequence to mimic postgres
this.db.connection.client.pool.on('acquireSuccess', async (eventId, resource) => {
resource.query('SET serial_normalization = "sql_sequence";');
resource.query('SET default_int_size = 4;');
});
}

usesForeignKeys() {
return true;
}

getSqlType(type) {
switch (type) {
case 'timestamp': {
return 'datetime';
}
default: {
return type;
}
}
}

transformErrors(error) {
switch (error.code) {
case '23502': {
throw new errors.NotNullConstraint({ column: error.column });
}
default: {
super.transformErrors(error);
}
}
}
}

module.exports = CockroachDialect;
243 changes: 243 additions & 0 deletions packages/core/database/lib/dialects/cockroachdb/schema-inspector.js
@@ -0,0 +1,243 @@
'use strict';

const SQL_QUERIES = {
TABLE_LIST: /* sql */ `
SELECT *
FROM information_schema.tables
WHERE
table_schema = ?
AND table_type = 'BASE TABLE'
AND table_name != 'geometry_columns'
AND table_name != 'spatial_ref_sys';
`,
LIST_COLUMNS: /* sql */ `
SELECT data_type, column_name, character_maximum_length, column_default, is_nullable
FROM information_schema.columns
WHERE table_schema = ? AND table_name = ?;
`,
INDEX_LIST: /* sql */ `
SELECT
ix.indexrelid,
i.relname as index_name,
a.attname as column_name,
ix.indisunique as is_unique,
ix.indisprimary as is_primary
FROM
pg_class t,
pg_namespace s,
pg_class i,
pg_index ix,
pg_attribute a
WHERE
t.oid = ix.indrelid
AND i.oid = ix.indexrelid
AND a.attrelid = t.oid
AND a.attnum = ANY(ix.indkey)
AND t.relkind = 'r'
AND t.relnamespace = s.oid
AND s.nspname = ?
AND t.relname = ?;
`,
FOREIGN_KEY_LIST: /* sql */ `
SELECT
tco."constraint_name" as constraint_name,
kcu."column_name" as column_name,
rel_kcu."table_name" as foreign_table,
rel_kcu."column_name" as fk_column_name,
rco.update_rule as on_update,
rco.delete_rule as on_delete
FROM information_schema.table_constraints tco
JOIN information_schema.key_column_usage kcu
ON tco.constraint_schema = kcu.constraint_schema
AND tco.constraint_name = kcu.constraint_name
JOIN information_schema.referential_constraints rco
ON tco.constraint_schema = rco.constraint_schema
AND tco.constraint_name = rco.constraint_name
JOIN information_schema.key_column_usage rel_kcu
ON rco.unique_constraint_schema = rel_kcu.constraint_schema
AND rco.unique_constraint_name = rel_kcu.constraint_name
AND kcu.ordinal_position = rel_kcu.ordinal_position
WHERE
tco.constraint_type = 'FOREIGN KEY'
AND tco.constraint_schema = ?
AND tco.table_name = ?
ORDER BY kcu.table_schema, kcu.table_name, kcu.ordinal_position, kcu.constraint_name;
`,
};

const toStrapiType = (column) => {
const rootType = column.data_type.toLowerCase().match(/[^(), ]+/)[0];

switch (rootType) {
case 'integer': {
// find a way to figure out the increments
return { type: 'integer' };
}
case 'text': {
return { type: 'text', args: ['longtext'] };
}
case 'boolean': {
return { type: 'boolean' };
}
case 'character': {
return { type: 'string', args: [column.character_maximum_length] };
}
case 'timestamp': {
return { type: 'datetime', args: [{ useTz: false, precision: 6 }] };
}
case 'date': {
return { type: 'date' };
}
case 'time': {
return { type: 'time', args: [{ precision: 3 }] };
}
case 'numeric': {
return { type: 'decimal', args: [10, 2] };
}
case 'real':
case 'double': {
return { type: 'double' };
}
case 'bigint': {
return { type: 'bigInteger' };
}
case 'jsonb': {
return { type: 'jsonb' };
}
default: {
return { type: 'specificType', args: [column.data_type] };
}
}
};
const getIndexType = (index) => {
if (index.is_primary) {
return 'primary';
}

if (index.is_unique) {
return 'unique';
}

return null;
};

class CockroachdbSchemaInspector {
constructor(db) {
this.db = db;
}

async getSchema() {
const schema = { tables: [] };

const tables = await this.getTables();

schema.tables = await Promise.all(
tables.map(async (tableName) => {
const columns = await this.getColumns(tableName);
const indexes = await this.getIndexes(tableName);
const foreignKeys = await this.getForeignKeys(tableName);

return {
name: tableName,
columns,
indexes,
foreignKeys,
};
})
);

return schema;
}

getDatabaseSchema() {
return this.db.connection.getSchemaName() || 'public';
}

async getTables() {
const { rows } = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST, [
this.getDatabaseSchema(),
]);

return rows.map((row) => row.table_name);
}

async getColumns(tableName) {
const { rows } = await this.db.connection.raw(SQL_QUERIES.LIST_COLUMNS, [
this.getDatabaseSchema(),
tableName,
]);

return rows.map((row) => {
const { type, args = [], ...rest } = toStrapiType(row);

const defaultTo =
row.column_default && row.column_default.includes('nextval(') ? null : row.column_default;

return {
type,
args,
defaultTo,
name: row.column_name,
notNullable: row.is_nullable === 'NO',
unsigned: false,
...rest,
};
});
}

async getIndexes(tableName) {
const { rows } = await this.db.connection.raw(SQL_QUERIES.INDEX_LIST, [
this.getDatabaseSchema(),
tableName,
]);

const ret = {};

for (const index of rows) {
if (index.column_name === 'id') {
continue;
}

if (!ret[index.indexrelid]) {
ret[index.indexrelid] = {
columns: [index.column_name],
name: index.index_name,
type: getIndexType(index),
};
} else {
ret[index.indexrelid].columns.push(index.column_name);
}
}

return Object.values(ret);
}

async getForeignKeys(tableName) {
const { rows } = await this.db.connection.raw(SQL_QUERIES.FOREIGN_KEY_LIST, [
this.getDatabaseSchema(),
tableName,
]);

const ret = {};

for (const fk of rows) {
if (!ret[fk.constraint_name]) {
ret[fk.constraint_name] = {
name: fk.constraint_name,
columns: [fk.column_name],
referencedColumns: [fk.fk_column_name],
referencedTable: fk.foreign_table,
onUpdate: fk.on_update.toUpperCase(),
onDelete: fk.on_delete.toUpperCase(),
};
} else {
ret[fk.constraint_name].columns.push(fk.column_name);
ret[fk.constraint_name].referencedColumns.push(fk.fk_column_name);
}
}

return Object.values(ret);
}
}

module.exports = CockroachdbSchemaInspector;