dynalitics 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +261 -2
- package/dist/adapter-version.d.ts +3 -0
- package/dist/adapter-version.js +3 -0
- package/dist/adapter.d.ts +12 -0
- package/dist/adapter.js +7 -0
- package/dist/cli/io.d.ts +9 -0
- package/dist/cli/io.js +10 -0
- package/dist/cli/run.d.ts +4 -0
- package/dist/cli/run.js +39 -0
- package/dist/cli/sql.d.ts +5 -0
- package/dist/cli/sql.js +129 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +5 -0
- package/dist/config/schema.d.ts +64 -0
- package/dist/config/schema.js +248 -0
- package/dist/config/validate.d.ts +30 -0
- package/dist/config/validate.js +68 -0
- package/dist/create.d.ts +16 -0
- package/dist/create.js +308 -0
- package/dist/dialect/common.d.ts +54 -0
- package/dist/dialect/common.js +197 -0
- package/dist/dialect/index.d.ts +5 -0
- package/dist/dialect/index.js +8 -0
- package/dist/dialect/mssql.d.ts +3 -0
- package/dist/dialect/mssql.js +104 -0
- package/dist/dialect/mysql.d.ts +3 -0
- package/dist/dialect/mysql.js +140 -0
- package/dist/dialect/postgres.d.ts +5 -0
- package/dist/dialect/postgres.js +90 -0
- package/dist/dialect/types.d.ts +79 -0
- package/dist/dialect/types.js +2 -0
- package/dist/driver/types.d.ts +74 -0
- package/dist/driver/types.js +66 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.js +39 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3 -0
- package/dist/internals.d.ts +20 -0
- package/dist/internals.js +8 -0
- package/dist/logger.d.ts +10 -0
- package/dist/logger.js +24 -0
- package/dist/model/column-model.d.ts +7 -0
- package/dist/model/column-model.js +240 -0
- package/dist/model/identifiers.d.ts +19 -0
- package/dist/model/identifiers.js +62 -0
- package/dist/model/physical.d.ts +19 -0
- package/dist/model/physical.js +70 -0
- package/dist/model/types.d.ts +92 -0
- package/dist/model/types.js +9 -0
- package/dist/protocol.d.ts +222 -0
- package/dist/protocol.js +5 -0
- package/dist/queue/pipeline.d.ts +46 -0
- package/dist/queue/pipeline.js +282 -0
- package/dist/read/adapters.d.ts +7 -0
- package/dist/read/adapters.js +75 -0
- package/dist/read/buckets.d.ts +7 -0
- package/dist/read/buckets.js +39 -0
- package/dist/read/capabilities.d.ts +39 -0
- package/dist/read/capabilities.js +65 -0
- package/dist/read/handler.d.ts +48 -0
- package/dist/read/handler.js +63 -0
- package/dist/read/http.d.ts +18 -0
- package/dist/read/http.js +65 -0
- package/dist/read/index.d.ts +5 -0
- package/dist/read/index.js +3 -0
- package/dist/read/lock/authorize.d.ts +22 -0
- package/dist/read/lock/authorize.js +32 -0
- package/dist/read/lock/base-path.d.ts +11 -0
- package/dist/read/lock/base-path.js +12 -0
- package/dist/read/lock/gate.d.ts +30 -0
- package/dist/read/lock/gate.js +24 -0
- package/dist/read/lock/index.d.ts +28 -0
- package/dist/read/lock/index.js +46 -0
- package/dist/read/lock/options.d.ts +13 -0
- package/dist/read/lock/options.js +18 -0
- package/dist/read/lock/serve-html.d.ts +6 -0
- package/dist/read/lock/serve-html.js +38 -0
- package/dist/read/mask.d.ts +12 -0
- package/dist/read/mask.js +55 -0
- package/dist/read/params.d.ts +56 -0
- package/dist/read/params.js +150 -0
- package/dist/read/physical.d.ts +11 -0
- package/dist/read/physical.js +23 -0
- package/dist/read/queries.d.ts +75 -0
- package/dist/read/queries.js +316 -0
- package/dist/read/report.d.ts +21 -0
- package/dist/read/report.js +32 -0
- package/dist/read/routes.d.ts +38 -0
- package/dist/read/routes.js +261 -0
- package/dist/read/scope.d.ts +11 -0
- package/dist/read/scope.js +47 -0
- package/dist/read/search/fields.d.ts +24 -0
- package/dist/read/search/fields.js +67 -0
- package/dist/read/search/parse.d.ts +48 -0
- package/dist/read/search/parse.js +124 -0
- package/dist/read/semaphore.d.ts +6 -0
- package/dist/read/semaphore.js +34 -0
- package/dist/read/service.d.ts +42 -0
- package/dist/read/service.js +266 -0
- package/dist/read/types.d.ts +62 -0
- package/dist/read/types.js +2 -0
- package/dist/redact.d.ts +8 -0
- package/dist/redact.js +60 -0
- package/dist/report.d.ts +12 -0
- package/dist/report.js +39 -0
- package/dist/schema/generate.d.ts +46 -0
- package/dist/schema/generate.js +71 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +3 -0
- package/dist/startup/compare.d.ts +22 -0
- package/dist/startup/compare.js +165 -0
- package/dist/startup/fix-sql.d.ts +15 -0
- package/dist/startup/fix-sql.js +94 -0
- package/dist/startup/privileges.d.ts +21 -0
- package/dist/startup/privileges.js +63 -0
- package/dist/startup/session-timezone.d.ts +10 -0
- package/dist/startup/session-timezone.js +19 -0
- package/dist/startup/type-families.d.ts +10 -0
- package/dist/startup/type-families.js +55 -0
- package/dist/startup/unmapped-notice.d.ts +5 -0
- package/dist/startup/unmapped-notice.js +28 -0
- package/dist/track/build-row.d.ts +50 -0
- package/dist/track/build-row.js +208 -0
- package/dist/track/custom.d.ts +31 -0
- package/dist/track/custom.js +83 -0
- package/dist/track/denied-view.d.ts +14 -0
- package/dist/track/denied-view.js +21 -0
- package/dist/track/first-visit.d.ts +19 -0
- package/dist/track/first-visit.js +70 -0
- package/dist/track/ip.d.ts +17 -0
- package/dist/track/ip.js +87 -0
- package/dist/track/limits.d.ts +7 -0
- package/dist/track/limits.js +26 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.js +2 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +3 -0
- package/package.json +71 -4
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type TableRef } from '../model/identifiers.js';
|
|
2
|
+
import type { ColumnDef, ColumnModel, Dialect } from '../model/types.js';
|
|
3
|
+
import type { CompiledQuery, ReadSyntax, Row, SqlDialect } from './types.js';
|
|
4
|
+
export declare const PLACEHOLDER_ROLE: RegExp;
|
|
5
|
+
declare const UNLIMITED: number;
|
|
6
|
+
/** Everything that differs between dialects. The generic builder below turns this into a SqlDialect. */
|
|
7
|
+
export interface DialectSyntax {
|
|
8
|
+
readonly name: Dialect;
|
|
9
|
+
readonly limits: SqlDialect['limits'];
|
|
10
|
+
readonly open: string;
|
|
11
|
+
readonly close: string;
|
|
12
|
+
readonly explicitNull: boolean;
|
|
13
|
+
readonly read: ReadSyntax;
|
|
14
|
+
placeholder(index: number): string;
|
|
15
|
+
jsonCast?(placeholder: string): string;
|
|
16
|
+
timestampParam(d: Date): string;
|
|
17
|
+
columnType(c: ColumnDef): string;
|
|
18
|
+
/** Full definition for the id column, constraints included. */
|
|
19
|
+
idDefinition: string;
|
|
20
|
+
/** Collation clause that goes right after the type ('' if none). */
|
|
21
|
+
collation(c: ColumnDef): string;
|
|
22
|
+
/** Constraint that goes after nullability, e.g. SQL Server's ISJSON check ('' if none). */
|
|
23
|
+
constraint(c: ColumnDef, model: ColumnModel): string;
|
|
24
|
+
tableSuffix(): string;
|
|
25
|
+
tableConstraints(model: ColumnModel, quote: (n: string) => string): readonly string[];
|
|
26
|
+
addColumnKeyword: string;
|
|
27
|
+
dropNotNull(table: string, name: string, type: string): string;
|
|
28
|
+
alterType(table: string, name: string, type: string): string;
|
|
29
|
+
supportsPartialIndex: boolean;
|
|
30
|
+
introspectSql(table: TableRef, q: (n: string) => string): CompiledQuery;
|
|
31
|
+
privilegeSql(table: TableRef, qualified: string): readonly CompiledQuery[];
|
|
32
|
+
parsePrivileges(results: readonly (readonly Row[])[], table: TableRef): ReadonlySet<string>;
|
|
33
|
+
grantTarget(table: TableRef, q: (n: string) => string): string;
|
|
34
|
+
grantSelectColumns(target: string, columns: readonly string[], role: string, q: (n: string) => string): string;
|
|
35
|
+
renderRole(role: string, q: (n: string) => string): string;
|
|
36
|
+
grantPreamble(table: TableRef, writer: string, reader: string, q: (n: string) => string): readonly string[];
|
|
37
|
+
}
|
|
38
|
+
export declare function indexName(table: string, suffix: string, max: number): string;
|
|
39
|
+
interface IndexSpec {
|
|
40
|
+
readonly suffix: string;
|
|
41
|
+
readonly columns: readonly string[];
|
|
42
|
+
readonly whereNotNull?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Recommended indexes (spec 6.3): range scans, and the "any earlier row?" probes of read-time first-visit. */
|
|
45
|
+
export declare function indexSpecs(model: ColumnModel): readonly IndexSpec[];
|
|
46
|
+
export declare const qualify: (t: TableRef, q: (n: string) => string) => string;
|
|
47
|
+
export declare function createDialect(s: DialectSyntax): SqlDialect;
|
|
48
|
+
/** Shared role rendering: placeholders like <writer_role> pass through, real names must be valid identifiers. */
|
|
49
|
+
export declare function renderRoleWith(role: string, wrap: (name: string) => string): string;
|
|
50
|
+
export { UNLIMITED };
|
|
51
|
+
/** Guards for the few non-identifier values that are inlined into read SQL. */
|
|
52
|
+
export declare function assertLimit(n: number): number;
|
|
53
|
+
export declare function assertJsonKey(key: string): string;
|
|
54
|
+
//# sourceMappingURL=common.d.ts.map
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { DynaliticsConfigError } from '../errors.js';
|
|
3
|
+
import { assertIdentifier, isCustomKey, isIdentifier, } from '../model/identifiers.js';
|
|
4
|
+
import { isIntegerColumn } from '../model/physical.js';
|
|
5
|
+
export const PLACEHOLDER_ROLE = /^<[a-z_]+>$/;
|
|
6
|
+
const UNLIMITED = Number.MAX_SAFE_INTEGER;
|
|
7
|
+
const toBool = (v) => v === true || v === 1 || v === '1' || v === 't' || v === 'YES';
|
|
8
|
+
const toNum = (v) => v === null || v === undefined || v === '' ? null : Number.isFinite(Number(v)) ? Number(v) : null;
|
|
9
|
+
function describeComment(c) {
|
|
10
|
+
if (c.role === 'isFirstVisit')
|
|
11
|
+
return '-- derived from personal identifiers; NULL without consent';
|
|
12
|
+
if (c.role === 'custom')
|
|
13
|
+
return '-- declared custom fields; personal ones are NULL without consent';
|
|
14
|
+
if (c.role === 'ip')
|
|
15
|
+
return '-- PERSONAL DATA (stored per ip.mode)';
|
|
16
|
+
return c.personal ? '-- PERSONAL DATA' : '';
|
|
17
|
+
}
|
|
18
|
+
export function indexName(table, suffix, max) {
|
|
19
|
+
const full = `${table}_${suffix}`;
|
|
20
|
+
if (full.length <= max)
|
|
21
|
+
return full;
|
|
22
|
+
const digest = createHash('sha1').update(full).digest('hex').slice(0, 8);
|
|
23
|
+
return `${full.slice(0, max - 9)}_${digest}`;
|
|
24
|
+
}
|
|
25
|
+
/** Recommended indexes (spec 6.3): range scans, and the "any earlier row?" probes of read-time first-visit. */
|
|
26
|
+
export function indexSpecs(model) {
|
|
27
|
+
const ts = model.columns.find((c) => c.role === 'timestamp');
|
|
28
|
+
const id = model.columns.find((c) => c.role === 'id');
|
|
29
|
+
if (ts === undefined)
|
|
30
|
+
return [];
|
|
31
|
+
const specs = [
|
|
32
|
+
{ suffix: 'visited_at_idx', columns: id === undefined ? [ts.name] : [ts.name, id.name] },
|
|
33
|
+
];
|
|
34
|
+
const { userId, ip } = model.identity;
|
|
35
|
+
if (userId !== undefined) {
|
|
36
|
+
specs.push({
|
|
37
|
+
suffix: 'user_id_idx',
|
|
38
|
+
columns: [userId.name, ts.name],
|
|
39
|
+
whereNotNull: userId.name,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (ip !== undefined)
|
|
43
|
+
specs.push({ suffix: 'ip_idx', columns: [ip.name, ts.name], whereNotNull: ip.name });
|
|
44
|
+
return specs;
|
|
45
|
+
}
|
|
46
|
+
export const qualify = (t, q) => [t.database, t.schema, t.name]
|
|
47
|
+
.filter((p) => p !== undefined)
|
|
48
|
+
.map(q)
|
|
49
|
+
.join('.');
|
|
50
|
+
export function createDialect(s) {
|
|
51
|
+
const quoteIdent = (name) => {
|
|
52
|
+
assertIdentifier(name, 'identifier');
|
|
53
|
+
return `${s.open}${name}${s.close}`;
|
|
54
|
+
};
|
|
55
|
+
const qualifiedTable = (t) => [t.database, t.schema, t.name]
|
|
56
|
+
.filter((p) => p !== undefined)
|
|
57
|
+
.map(quoteIdent)
|
|
58
|
+
.join('.');
|
|
59
|
+
const columnType = (c) => s.columnType(c);
|
|
60
|
+
const columnDefinition = (c, model) => {
|
|
61
|
+
if (c.type.kind === 'id')
|
|
62
|
+
return s.idDefinition;
|
|
63
|
+
const nullPart = c.nullable ? (s.explicitNull ? 'NULL' : '') : 'NOT NULL';
|
|
64
|
+
return [`${columnType(c)} ${s.collation(c)}`.trim(), nullPart, s.constraint(c, model)]
|
|
65
|
+
.filter((part) => part !== '')
|
|
66
|
+
.join(' ');
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
name: s.name,
|
|
70
|
+
limits: s.limits,
|
|
71
|
+
quoteIdent,
|
|
72
|
+
qualifiedTable,
|
|
73
|
+
read: s.read,
|
|
74
|
+
placeholder: (i) => s.placeholder(i),
|
|
75
|
+
columnType,
|
|
76
|
+
timestampParam: (d) => s.timestampParam(d),
|
|
77
|
+
buildInsert(model, rows) {
|
|
78
|
+
const cols = model.insertColumns;
|
|
79
|
+
const params = [];
|
|
80
|
+
const tuples = rows.map((row) => {
|
|
81
|
+
if (row.length !== cols.length) {
|
|
82
|
+
throw new Error(`expected ${cols.length} values per row, got ${row.length}`);
|
|
83
|
+
}
|
|
84
|
+
const phs = row.map((value, idx) => {
|
|
85
|
+
// A boolean headed for a real integer column (tinyint(1), smallint) is bound as 1/0.
|
|
86
|
+
const target = cols[idx];
|
|
87
|
+
params.push(typeof value === 'boolean' && target?.type.kind === 'boolean' && isIntegerColumn(target)
|
|
88
|
+
? value
|
|
89
|
+
? 1
|
|
90
|
+
: 0
|
|
91
|
+
: value);
|
|
92
|
+
const ph = s.placeholder(params.length);
|
|
93
|
+
return cols[idx]?.type.kind === 'json' && s.jsonCast !== undefined ? s.jsonCast(ph) : ph;
|
|
94
|
+
});
|
|
95
|
+
return `(${phs.join(', ')})`;
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
sql: `INSERT INTO ${qualifiedTable(model.table)} (${cols.map((c) => quoteIdent(c.name)).join(', ')}) VALUES ${tuples.join(', ')}`,
|
|
99
|
+
params,
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
firstVisitLookup(model, kind, values) {
|
|
103
|
+
const col = kind === 'u' ? model.identity.userId : model.identity.ip;
|
|
104
|
+
if (col === undefined)
|
|
105
|
+
throw new Error('identity column is not mapped');
|
|
106
|
+
const phs = values.map((_, i) => s.placeholder(i + 1)).join(', ');
|
|
107
|
+
const userCol = model.identity.userId;
|
|
108
|
+
const extra = kind === 'i' && userCol !== undefined ? ` AND ${quoteIdent(userCol.name)} IS NULL` : '';
|
|
109
|
+
return {
|
|
110
|
+
sql: `SELECT DISTINCT ${quoteIdent(col.name)} AS v FROM ${qualifiedTable(model.table)} WHERE ${quoteIdent(col.name)} IN (${phs})${extra}`,
|
|
111
|
+
params: [...values],
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
introspectSql: (t) => s.introspectSql(t, quoteIdent),
|
|
115
|
+
mapColumnRow(row) {
|
|
116
|
+
const maxLength = toNum(row['max_length']);
|
|
117
|
+
return {
|
|
118
|
+
name: String(row['name']),
|
|
119
|
+
dataType: String(row['data_type']).toLowerCase(),
|
|
120
|
+
maxLength,
|
|
121
|
+
nullable: toBool(row['is_nullable']),
|
|
122
|
+
hasDefault: toBool(row['has_default']),
|
|
123
|
+
generated: toBool(row['is_generated']),
|
|
124
|
+
precision: toNum(row['dt_precision']),
|
|
125
|
+
tableType: typeof row['table_type'] === 'string' ? row['table_type'] : 'BASE TABLE',
|
|
126
|
+
...(typeof row['schema_name'] === 'string' ? { schema: row['schema_name'] } : {}),
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
privilegeSql: (t) => s.privilegeSql(t, qualifiedTable(t)),
|
|
130
|
+
parsePrivileges: (results, t) => s.parsePrivileges(results, t),
|
|
131
|
+
createTableSql(model) {
|
|
132
|
+
const lines = model.columns.map((c) => ({
|
|
133
|
+
text: `${quoteIdent(c.name).padEnd(18)} ${columnDefinition(c, model)}`,
|
|
134
|
+
comment: describeComment(c),
|
|
135
|
+
}));
|
|
136
|
+
const constraints = s.tableConstraints(model, quoteIdent);
|
|
137
|
+
const all = [...lines, ...constraints.map((text) => ({ text, comment: '' }))];
|
|
138
|
+
const body = all
|
|
139
|
+
.map((l, i) => ` ${l.text}${i < all.length - 1 ? ',' : ''}${l.comment === '' ? '' : ` ${l.comment}`}`)
|
|
140
|
+
.join('\n');
|
|
141
|
+
return `CREATE TABLE ${qualifiedTable(model.table)} (\n${body}\n)${s.tableSuffix()};`;
|
|
142
|
+
},
|
|
143
|
+
createIndexSql(model) {
|
|
144
|
+
const table = qualifiedTable(model.table);
|
|
145
|
+
return indexSpecs(model).map((spec) => {
|
|
146
|
+
const name = quoteIdent(indexName(model.table.name, spec.suffix, s.limits.maxIdentifier));
|
|
147
|
+
const where = spec.whereNotNull !== undefined && s.supportsPartialIndex
|
|
148
|
+
? ` WHERE ${quoteIdent(spec.whereNotNull)} IS NOT NULL`
|
|
149
|
+
: '';
|
|
150
|
+
return `CREATE INDEX ${name} ON ${table} (${spec.columns.map(quoteIdent).join(', ')})${where};`;
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
grantSql(opts) {
|
|
154
|
+
const writer = s.renderRole(opts.writerRole, quoteIdent);
|
|
155
|
+
const reader = s.renderRole(opts.readerRole, quoteIdent);
|
|
156
|
+
const target = s.grantTarget(opts.table, quoteIdent);
|
|
157
|
+
const out = [...s.grantPreamble(opts.table, writer, reader, quoteIdent)];
|
|
158
|
+
out.push('-- Application (writer): INSERT only. It cannot read anything back.');
|
|
159
|
+
out.push(`GRANT INSERT ON ${target} TO ${writer};`);
|
|
160
|
+
if (opts.writerSelectColumns !== undefined && opts.writerSelectColumns.length > 0) {
|
|
161
|
+
out.push('-- Needed only because firstVisit.enabled is on (write-time detection): column-level SELECT on identity columns.');
|
|
162
|
+
out.push(s.grantSelectColumns(target, opts.writerSelectColumns, writer, quoteIdent));
|
|
163
|
+
}
|
|
164
|
+
out.push('-- Dashboard (reader): SELECT only. This role can see PERSONAL DATA columns; protect it accordingly.');
|
|
165
|
+
out.push(`GRANT SELECT ON ${target} TO ${reader};`);
|
|
166
|
+
return out;
|
|
167
|
+
},
|
|
168
|
+
addColumnSql: (t, c) => `ALTER TABLE ${qualifiedTable(t)} ${s.addColumnKeyword} ${quoteIdent(c.name)} ${columnType(c)};`,
|
|
169
|
+
dropNotNullSql: (t, c) => s.dropNotNull(qualifiedTable(t), quoteIdent(c.name), columnType(c)),
|
|
170
|
+
alterTypeSql: (t, c) => s.alterType(qualifiedTable(t), quoteIdent(c.name), columnType(c)),
|
|
171
|
+
renderRole: (role) => s.renderRole(role, quoteIdent),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** Shared role rendering: placeholders like <writer_role> pass through, real names must be valid identifiers. */
|
|
175
|
+
export function renderRoleWith(role, wrap) {
|
|
176
|
+
if (PLACEHOLDER_ROLE.test(role))
|
|
177
|
+
return role;
|
|
178
|
+
if (!isIdentifier(role)) {
|
|
179
|
+
throw new DynaliticsConfigError([
|
|
180
|
+
`Role ${JSON.stringify(role)} is not supported. Use letters, digits and underscore (max 63), or leave the <placeholder>.`,
|
|
181
|
+
]);
|
|
182
|
+
}
|
|
183
|
+
return wrap(role);
|
|
184
|
+
}
|
|
185
|
+
export { UNLIMITED };
|
|
186
|
+
/** Guards for the few non-identifier values that are inlined into read SQL. */
|
|
187
|
+
export function assertLimit(n) {
|
|
188
|
+
if (!Number.isInteger(n) || n < 1 || n > 100_000)
|
|
189
|
+
throw new Error('limit must be a positive integer');
|
|
190
|
+
return n;
|
|
191
|
+
}
|
|
192
|
+
export function assertJsonKey(key) {
|
|
193
|
+
if (!isCustomKey(key))
|
|
194
|
+
throw new Error('invalid custom field key');
|
|
195
|
+
return key;
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=common.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Dialect } from '../model/types.js';
|
|
2
|
+
import type { SqlDialect } from './types.js';
|
|
3
|
+
export declare function getDialect(name: Dialect): SqlDialect;
|
|
4
|
+
export type { SqlDialect, CompiledQuery, ColumnInfo, Row, GrantOptions } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { mssql } from './mssql.js';
|
|
2
|
+
import { mysql } from './mysql.js';
|
|
3
|
+
import { postgres } from './postgres.js';
|
|
4
|
+
const DIALECTS = { postgres, mysql, mssql };
|
|
5
|
+
export function getDialect(name) {
|
|
6
|
+
return DIALECTS[name];
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { assertJsonKey, assertLimit, createDialect, qualify, renderRoleWith } from './common.js';
|
|
2
|
+
const BIN_COLLATION = 'COLLATE Latin1_General_100_BIN2';
|
|
3
|
+
function columnType(c) {
|
|
4
|
+
switch (c.type.kind) {
|
|
5
|
+
case 'id':
|
|
6
|
+
return 'BIGINT';
|
|
7
|
+
case 'timestamp':
|
|
8
|
+
return 'DATETIME2(3)';
|
|
9
|
+
case 'boolean':
|
|
10
|
+
return 'BIT';
|
|
11
|
+
case 'string':
|
|
12
|
+
return `NVARCHAR(${c.type.maxLength})`;
|
|
13
|
+
case 'integer':
|
|
14
|
+
return 'BIGINT';
|
|
15
|
+
case 'number':
|
|
16
|
+
return 'FLOAT(53)';
|
|
17
|
+
case 'json':
|
|
18
|
+
return 'NVARCHAR(MAX)';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export const mssql = createDialect({
|
|
22
|
+
name: 'mssql',
|
|
23
|
+
limits: { maxParams: 2100, maxRowsPerInsert: 1000, maxIdentifier: 128 },
|
|
24
|
+
open: '[',
|
|
25
|
+
close: ']',
|
|
26
|
+
explicitNull: true,
|
|
27
|
+
read: {
|
|
28
|
+
bucket: (col, interval) => {
|
|
29
|
+
const floor = {
|
|
30
|
+
hour: `DATEADD(hour, DATEDIFF(hour, 0, ${col}), 0)`,
|
|
31
|
+
day: `DATEADD(day, DATEDIFF(day, 0, ${col}), 0)`,
|
|
32
|
+
// 1900-01-01 is a Monday and DATEDIFF(day) ignores DATEFIRST, so weeks never depend on session settings.
|
|
33
|
+
week: `DATEADD(day, (DATEDIFF(day, '19000101', ${col}) / 7) * 7, '19000101')`,
|
|
34
|
+
month: `DATEADD(month, DATEDIFF(month, 0, ${col}), 0)`,
|
|
35
|
+
}[interval];
|
|
36
|
+
// Style 126 drops the fraction when it is zero (always, for a bucket start), so the fraction is written
|
|
37
|
+
// explicitly: every bucket key must equal the JavaScript toISOString() form exactly (found by the first CI run).
|
|
38
|
+
return `CONVERT(varchar(19), ${floor}, 126) + '.000Z'`;
|
|
39
|
+
},
|
|
40
|
+
// Same reason: style 126 would print 12:01:44Z for a visit at exactly .000 seconds.
|
|
41
|
+
tsText: (col) => `CONVERT(varchar(19), ${col}, 126) + '.' + RIGHT('00' + CAST(DATEPART(millisecond, ${col}) AS varchar(3)), 3) + 'Z'`,
|
|
42
|
+
isTrue: (col) => `${col} = 1`,
|
|
43
|
+
isFalse: (col) => `${col} = 0`,
|
|
44
|
+
jsonKey: (col, key) => `JSON_VALUE(${col}, '$.${assertJsonKey(key)}')`,
|
|
45
|
+
jsonNumber: (col, key) => `TRY_CAST(JSON_VALUE(${col}, '$.${assertJsonKey(key)}') AS float)`,
|
|
46
|
+
castText: (col, kind) => kind === 'string'
|
|
47
|
+
? col
|
|
48
|
+
: kind === 'boolean'
|
|
49
|
+
? `CASE WHEN ${col} = 1 THEN 'true' WHEN ${col} = 0 THEN 'false' END`
|
|
50
|
+
: `CAST(${col} AS NVARCHAR(4000))`,
|
|
51
|
+
count: 'COUNT_BIG(*)',
|
|
52
|
+
top: (n) => `TOP ${assertLimit(n)} `,
|
|
53
|
+
limit: () => '',
|
|
54
|
+
},
|
|
55
|
+
placeholder: (i) => `@p${i}`,
|
|
56
|
+
timestampParam: (d) => d.toISOString().replace('Z', ''),
|
|
57
|
+
columnType,
|
|
58
|
+
idDefinition: 'BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY',
|
|
59
|
+
collation: (c) => c.role === 'pageVisited' || c.role === 'ip' || c.role === 'userId' ? BIN_COLLATION : '',
|
|
60
|
+
constraint: (c, model) => c.role === 'custom'
|
|
61
|
+
? `CONSTRAINT [CK_${model.table.name}_custom_json] CHECK ([${c.name}] IS NULL OR ISJSON([${c.name}]) = 1)`
|
|
62
|
+
: '',
|
|
63
|
+
tableSuffix: () => '',
|
|
64
|
+
tableConstraints: () => [],
|
|
65
|
+
addColumnKeyword: 'ADD',
|
|
66
|
+
dropNotNull: (table, name, type) => `ALTER TABLE ${table} ALTER COLUMN ${name} ${type} NULL;`,
|
|
67
|
+
alterType: (table, name, type) => `ALTER TABLE ${table} ALTER COLUMN ${name} ${type} NULL;`,
|
|
68
|
+
supportsPartialIndex: true,
|
|
69
|
+
introspectSql: (table, q) => {
|
|
70
|
+
const catalog = table.database === undefined ? '' : `${q(table.database)}.`;
|
|
71
|
+
return {
|
|
72
|
+
sql: `SELECT c.COLUMN_NAME AS name, c.DATA_TYPE AS data_type, c.CHARACTER_MAXIMUM_LENGTH AS max_length,
|
|
73
|
+
CASE WHEN c.IS_NULLABLE = 'YES' THEN 1 ELSE 0 END AS is_nullable,
|
|
74
|
+
CASE WHEN c.COLUMN_DEFAULT IS NOT NULL THEN 1 ELSE 0 END AS has_default,
|
|
75
|
+
CASE WHEN c.DATA_TYPE = 'timestamp'
|
|
76
|
+
OR COLUMNPROPERTY(OBJECT_ID(QUOTENAME(c.TABLE_CATALOG) + '.' + QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME)), c.COLUMN_NAME, 'IsIdentity') = 1
|
|
77
|
+
OR COLUMNPROPERTY(OBJECT_ID(QUOTENAME(c.TABLE_CATALOG) + '.' + QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME)), c.COLUMN_NAME, 'IsComputed') = 1
|
|
78
|
+
THEN 1 ELSE 0 END AS is_generated,
|
|
79
|
+
c.DATETIME_PRECISION AS dt_precision, t.TABLE_TYPE AS table_type, c.TABLE_SCHEMA AS schema_name
|
|
80
|
+
FROM ${catalog}INFORMATION_SCHEMA.COLUMNS c
|
|
81
|
+
JOIN ${catalog}INFORMATION_SCHEMA.TABLES t
|
|
82
|
+
ON t.TABLE_CATALOG = c.TABLE_CATALOG AND t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
|
|
83
|
+
WHERE c.TABLE_SCHEMA = COALESCE(@p1, SCHEMA_NAME()) AND c.TABLE_NAME = @p2
|
|
84
|
+
ORDER BY c.ORDINAL_POSITION`,
|
|
85
|
+
params: [table.schema ?? null, table.name],
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
privilegeSql: (_table, qualified) => [
|
|
89
|
+
{
|
|
90
|
+
sql: `SELECT permission_name AS privilege FROM fn_my_permissions(@p1, 'OBJECT')`,
|
|
91
|
+
params: [qualified],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
sql: `SELECT 'SUPERUSER' AS privilege WHERE IS_SRVROLEMEMBER('sysadmin') = 1 OR IS_MEMBER('db_owner') = 1`,
|
|
95
|
+
params: [],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
parsePrivileges: (results) => new Set(results.flat().map((r) => String(r['privilege']).toUpperCase())),
|
|
99
|
+
grantTarget: (table, q) => `OBJECT::${qualify(table, q)}`,
|
|
100
|
+
grantSelectColumns: (target, columns, role, q) => `GRANT SELECT ON ${target} (${columns.map(q).join(', ')}) TO ${role};`,
|
|
101
|
+
renderRole: (role, q) => renderRoleWith(role, q),
|
|
102
|
+
grantPreamble: () => [],
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=mssql.js.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { UNLIMITED, assertJsonKey, assertLimit, createDialect, qualify, renderRoleWith, } from './common.js';
|
|
2
|
+
function columnType(c) {
|
|
3
|
+
switch (c.type.kind) {
|
|
4
|
+
case 'id':
|
|
5
|
+
return 'BIGINT UNSIGNED';
|
|
6
|
+
case 'timestamp':
|
|
7
|
+
return 'DATETIME(3)';
|
|
8
|
+
case 'boolean':
|
|
9
|
+
return 'TINYINT(1)';
|
|
10
|
+
case 'string':
|
|
11
|
+
return `VARCHAR(${c.type.maxLength})`;
|
|
12
|
+
case 'integer':
|
|
13
|
+
return 'BIGINT';
|
|
14
|
+
case 'number':
|
|
15
|
+
return 'DOUBLE';
|
|
16
|
+
case 'json':
|
|
17
|
+
return 'JSON';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const ALL_PRIVILEGES = [
|
|
21
|
+
'SELECT',
|
|
22
|
+
'INSERT',
|
|
23
|
+
'UPDATE',
|
|
24
|
+
'DELETE',
|
|
25
|
+
'DROP',
|
|
26
|
+
'ALTER',
|
|
27
|
+
'CREATE',
|
|
28
|
+
'REFERENCES',
|
|
29
|
+
'TRIGGER',
|
|
30
|
+
];
|
|
31
|
+
/** Best effort: parses `SHOW GRANTS` text for grants that apply to this table. Roles make it heuristic. */
|
|
32
|
+
function parseGrants(results, table) {
|
|
33
|
+
const text = (v) => (typeof v === 'string' ? v : '');
|
|
34
|
+
const grants = (results[0] ?? []).map((r) => text(Object.values(r)[0]));
|
|
35
|
+
const currentDb = text(results[1]?.[0]?.['db']);
|
|
36
|
+
const db = table.schema ?? currentDb;
|
|
37
|
+
const unescape = (s) => s.replace(/\\([_%])/g, '$1');
|
|
38
|
+
const found = new Set();
|
|
39
|
+
for (const line of grants) {
|
|
40
|
+
const m = /^GRANT (.+?) ON (.+?) TO /.exec(line);
|
|
41
|
+
if (m === null)
|
|
42
|
+
continue;
|
|
43
|
+
const scope = (m[2] ?? '').replace(/`/g, '');
|
|
44
|
+
const [scopeDb = '', scopeTable = ''] = scope.split('.');
|
|
45
|
+
const applies = scope === '*.*' ||
|
|
46
|
+
(unescape(scopeDb) === db && (scopeTable === '*' || scopeTable === table.name));
|
|
47
|
+
if (!applies)
|
|
48
|
+
continue;
|
|
49
|
+
const privs = (m[1] ?? '')
|
|
50
|
+
.replace(/\([^)]*\)/g, '')
|
|
51
|
+
.split(',')
|
|
52
|
+
.map((p) => p.trim().toUpperCase());
|
|
53
|
+
for (const p of privs) {
|
|
54
|
+
if (p === 'ALL PRIVILEGES' || p === 'ALL')
|
|
55
|
+
ALL_PRIVILEGES.forEach((x) => found.add(x));
|
|
56
|
+
else if (p !== 'USAGE' && p !== '')
|
|
57
|
+
found.add(p);
|
|
58
|
+
}
|
|
59
|
+
if (/WITH GRANT OPTION\s*$/.test(line))
|
|
60
|
+
found.add('GRANT OPTION');
|
|
61
|
+
}
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
export const mysql = createDialect({
|
|
65
|
+
name: 'mysql',
|
|
66
|
+
limits: { maxParams: 65_535, maxRowsPerInsert: UNLIMITED, maxIdentifier: 64 },
|
|
67
|
+
open: '`',
|
|
68
|
+
close: '`',
|
|
69
|
+
explicitNull: false,
|
|
70
|
+
read: {
|
|
71
|
+
bucket: (col, interval) => {
|
|
72
|
+
switch (interval) {
|
|
73
|
+
case 'hour':
|
|
74
|
+
return `DATE_FORMAT(${col}, '%Y-%m-%dT%H:00:00.000Z')`;
|
|
75
|
+
case 'day':
|
|
76
|
+
return `DATE_FORMAT(${col}, '%Y-%m-%dT00:00:00.000Z')`;
|
|
77
|
+
case 'week':
|
|
78
|
+
return `DATE_FORMAT(DATE_SUB(DATE(${col}), INTERVAL WEEKDAY(${col}) DAY), '%Y-%m-%dT00:00:00.000Z')`;
|
|
79
|
+
case 'month':
|
|
80
|
+
return `DATE_FORMAT(${col}, '%Y-%m-01T00:00:00.000Z')`;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
tsText: (col) => `CONCAT(DATE_FORMAT(${col}, '%Y-%m-%dT%H:%i:%s.'), LPAD(MICROSECOND(${col}) DIV 1000, 3, '0'), 'Z')`,
|
|
84
|
+
isTrue: (col) => `${col} = 1`,
|
|
85
|
+
isFalse: (col) => `${col} = 0`,
|
|
86
|
+
jsonKey: (col, key) => `JSON_UNQUOTE(JSON_EXTRACT(${col}, '$.${assertJsonKey(key)}'))`,
|
|
87
|
+
jsonNumber: (col, key) => {
|
|
88
|
+
const v = `JSON_EXTRACT(${col}, '$.${assertJsonKey(key)}')`;
|
|
89
|
+
return `CASE WHEN JSON_TYPE(${v}) IN ('INTEGER', 'UNSIGNED INTEGER', 'DOUBLE', 'DECIMAL') THEN CAST(${v} AS DOUBLE) END`;
|
|
90
|
+
},
|
|
91
|
+
castText: (col, kind) => kind === 'string'
|
|
92
|
+
? col
|
|
93
|
+
: kind === 'boolean'
|
|
94
|
+
? `CASE WHEN ${col} = 1 THEN 'true' WHEN ${col} = 0 THEN 'false' END`
|
|
95
|
+
: `CAST(${col} AS CHAR)`,
|
|
96
|
+
count: 'COUNT(*)',
|
|
97
|
+
top: () => '',
|
|
98
|
+
limit: (n) => ` LIMIT ${assertLimit(n)}`,
|
|
99
|
+
},
|
|
100
|
+
placeholder: () => '?',
|
|
101
|
+
timestampParam: (d) => d.toISOString().replace('T', ' ').replace('Z', ''),
|
|
102
|
+
columnType,
|
|
103
|
+
idDefinition: 'BIGINT UNSIGNED NOT NULL AUTO_INCREMENT',
|
|
104
|
+
collation: () => '',
|
|
105
|
+
constraint: () => '',
|
|
106
|
+
tableSuffix: () => ' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin',
|
|
107
|
+
tableConstraints: (model, q) => {
|
|
108
|
+
const id = model.columns.find((c) => c.role === 'id');
|
|
109
|
+
return id === undefined ? [] : [`PRIMARY KEY (${q(id.name)})`];
|
|
110
|
+
},
|
|
111
|
+
addColumnKeyword: 'ADD COLUMN',
|
|
112
|
+
dropNotNull: (table, name, type) => `ALTER TABLE ${table} MODIFY COLUMN ${name} ${type} NULL;`,
|
|
113
|
+
alterType: (table, name, type) => `ALTER TABLE ${table} MODIFY COLUMN ${name} ${type};`,
|
|
114
|
+
supportsPartialIndex: false,
|
|
115
|
+
introspectSql: (table) => ({
|
|
116
|
+
sql: `SELECT c.COLUMN_NAME AS name, c.DATA_TYPE AS data_type, c.CHARACTER_MAXIMUM_LENGTH AS max_length,
|
|
117
|
+
(c.IS_NULLABLE = 'YES') AS is_nullable, (c.COLUMN_DEFAULT IS NOT NULL) AS has_default,
|
|
118
|
+
(c.EXTRA LIKE '%auto_increment%' OR c.EXTRA LIKE '%GENERATED%') AS is_generated,
|
|
119
|
+
c.DATETIME_PRECISION AS dt_precision, t.TABLE_TYPE AS table_type, c.TABLE_SCHEMA AS schema_name
|
|
120
|
+
FROM information_schema.COLUMNS c
|
|
121
|
+
JOIN information_schema.TABLES t ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
|
|
122
|
+
WHERE c.TABLE_SCHEMA = COALESCE(?, DATABASE()) AND c.TABLE_NAME = ?
|
|
123
|
+
ORDER BY c.ORDINAL_POSITION`,
|
|
124
|
+
params: [table.schema ?? null, table.name],
|
|
125
|
+
}),
|
|
126
|
+
privilegeSql: () => [
|
|
127
|
+
{ sql: 'SHOW GRANTS FOR CURRENT_USER()', params: [] },
|
|
128
|
+
{ sql: 'SELECT DATABASE() AS db', params: [] },
|
|
129
|
+
],
|
|
130
|
+
parsePrivileges: (results, table) => parseGrants(results, table),
|
|
131
|
+
grantTarget: (table, q) => (table.schema === undefined ? q(table.name) : qualify(table, q)),
|
|
132
|
+
grantSelectColumns: (target, columns, role, q) => `GRANT SELECT (${columns.map(q).join(', ')}) ON ${target} TO ${role};`,
|
|
133
|
+
renderRole: (role, q) => renderRoleWith(role, (name) => `${q(name)}@\`%\``),
|
|
134
|
+
grantPreamble: (table) => table.schema === undefined
|
|
135
|
+
? [
|
|
136
|
+
'-- No schema given: these grants apply to the current database. Use schema.name to be explicit.',
|
|
137
|
+
]
|
|
138
|
+
: [],
|
|
139
|
+
});
|
|
140
|
+
//# sourceMappingURL=mysql.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { PLACEHOLDER_ROLE, UNLIMITED, assertJsonKey, assertLimit, createDialect, qualify, renderRoleWith, } from './common.js';
|
|
2
|
+
function columnType(c) {
|
|
3
|
+
switch (c.type.kind) {
|
|
4
|
+
case 'id':
|
|
5
|
+
return 'bigint';
|
|
6
|
+
case 'timestamp':
|
|
7
|
+
return 'timestamptz';
|
|
8
|
+
case 'boolean':
|
|
9
|
+
return 'boolean';
|
|
10
|
+
case 'string':
|
|
11
|
+
return `varchar(${c.type.maxLength})`;
|
|
12
|
+
case 'integer':
|
|
13
|
+
return 'bigint';
|
|
14
|
+
case 'number':
|
|
15
|
+
return 'double precision';
|
|
16
|
+
case 'json':
|
|
17
|
+
return 'jsonb';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export const postgres = createDialect({
|
|
21
|
+
name: 'postgres',
|
|
22
|
+
limits: { maxParams: 65_535, maxRowsPerInsert: UNLIMITED, maxIdentifier: 63 },
|
|
23
|
+
open: '"',
|
|
24
|
+
close: '"',
|
|
25
|
+
explicitNull: false,
|
|
26
|
+
read: {
|
|
27
|
+
bucket: (col, interval) => `to_char(date_trunc('${interval}', ${col} AT TIME ZONE 'UTC'), 'YYYY-MM-DD"T"HH24:MI:SS".000Z"')`,
|
|
28
|
+
tsText: (col) => `to_char(${col} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`,
|
|
29
|
+
isTrue: (col) => `${col} IS TRUE`,
|
|
30
|
+
isFalse: (col) => `${col} IS FALSE`,
|
|
31
|
+
jsonKey: (col, key) => `(${col} ->> '${assertJsonKey(key)}')`,
|
|
32
|
+
// SIMILAR TO, not ~: F-39 allows no '~' outside literals. PostgreSQL has no TRY_CAST.
|
|
33
|
+
jsonNumber: (col, key) => {
|
|
34
|
+
const v = `(${col} ->> '${assertJsonKey(key)}')`;
|
|
35
|
+
return `CASE WHEN ${v} SIMILAR TO '-?[0-9]+([.][0-9]+)?([eE][-+]?[0-9]+)?' THEN CAST(${v} AS double precision) END`;
|
|
36
|
+
},
|
|
37
|
+
castText: (col, kind) => kind === 'string'
|
|
38
|
+
? col
|
|
39
|
+
: kind === 'boolean'
|
|
40
|
+
? `CASE WHEN ${col} IS TRUE THEN 'true' WHEN ${col} IS FALSE THEN 'false' END`
|
|
41
|
+
: `CAST(${col} AS text)`,
|
|
42
|
+
count: 'COUNT(*)',
|
|
43
|
+
top: () => '',
|
|
44
|
+
limit: (n) => ` LIMIT ${assertLimit(n)}`,
|
|
45
|
+
},
|
|
46
|
+
placeholder: (i) => `$${i}`,
|
|
47
|
+
jsonCast: (ph) => `${ph}::jsonb`,
|
|
48
|
+
timestampParam: (d) => d.toISOString(),
|
|
49
|
+
columnType,
|
|
50
|
+
idDefinition: 'bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY',
|
|
51
|
+
collation: () => '',
|
|
52
|
+
constraint: () => '',
|
|
53
|
+
tableSuffix: () => '',
|
|
54
|
+
tableConstraints: () => [],
|
|
55
|
+
addColumnKeyword: 'ADD COLUMN',
|
|
56
|
+
dropNotNull: (table, name) => `ALTER TABLE ${table} ALTER COLUMN ${name} DROP NOT NULL;`,
|
|
57
|
+
alterType: (table, name, type) => `ALTER TABLE ${table} ALTER COLUMN ${name} TYPE ${type} USING ${name}::${type};`,
|
|
58
|
+
supportsPartialIndex: true,
|
|
59
|
+
introspectSql: (table) => ({
|
|
60
|
+
sql: `SELECT c.column_name AS name, c.udt_name AS data_type, c.character_maximum_length AS max_length,
|
|
61
|
+
(c.is_nullable = 'YES') AS is_nullable, (c.column_default IS NOT NULL) AS has_default,
|
|
62
|
+
(c.is_identity = 'YES' OR c.is_generated = 'ALWAYS') AS is_generated,
|
|
63
|
+
c.datetime_precision AS dt_precision, t.table_type AS table_type, c.table_schema AS schema_name
|
|
64
|
+
FROM information_schema.columns c
|
|
65
|
+
JOIN information_schema.tables t ON t.table_schema = c.table_schema AND t.table_name = c.table_name
|
|
66
|
+
WHERE c.table_schema = COALESCE($1::text, current_schema()) AND c.table_name = $2::text
|
|
67
|
+
ORDER BY c.ordinal_position`,
|
|
68
|
+
params: [table.schema ?? null, table.name],
|
|
69
|
+
}),
|
|
70
|
+
privilegeSql: (_table, qualified) => [
|
|
71
|
+
{
|
|
72
|
+
sql: `SELECT p AS privilege FROM unnest(ARRAY['SELECT','INSERT','UPDATE','DELETE','TRUNCATE','REFERENCES','TRIGGER']) AS p
|
|
73
|
+
WHERE has_table_privilege(current_user, $1::text, p)`,
|
|
74
|
+
params: [qualified],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
sql: `SELECT 'SUPERUSER' AS privilege FROM pg_roles WHERE rolname = current_user AND rolsuper`,
|
|
78
|
+
params: [],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
parsePrivileges: (results) => new Set(results.flat().map((r) => String(r['privilege']).toUpperCase())),
|
|
82
|
+
grantTarget: (table, q) => qualify(table, q),
|
|
83
|
+
grantSelectColumns: (target, columns, role, q) => `GRANT SELECT (${columns.map(q).join(', ')}) ON ${target} TO ${role};`,
|
|
84
|
+
renderRole: (role, q) => renderRoleWith(role, q),
|
|
85
|
+
grantPreamble: (table, writer, reader, q) => table.schema === undefined
|
|
86
|
+
? []
|
|
87
|
+
: [`GRANT USAGE ON SCHEMA ${q(table.schema)} TO ${writer}, ${reader};`],
|
|
88
|
+
});
|
|
89
|
+
export { PLACEHOLDER_ROLE };
|
|
90
|
+
//# sourceMappingURL=postgres.js.map
|