-
Notifications
You must be signed in to change notification settings - Fork 190
Typescript Definition #351
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
Comments
If you want to use it without type checking you can simply:
At least the compiler will not complain about it. |
With the newest mapped types in TypeScript, its actually possible to model node-sql There is a work-in-progress implementation here: https://github.com/doxout/anydb-sql/blob/4e4c0ff4a7f2efb7f820baaafea1f624f1ae0399/d.ts/anydb-sql.d.ts which can be used as a starting point. |
Personally, I see greatest benefit when types guide usage of column fields on table object. Ones columns are something that may change often, and we do want computer's help in refactoring via type-checking. Setting a goalWith this in mind, the following question comes surfaces: How table definition can produce table with the type that has custom columns on it? Let's take table: const t = sql.define({
name: 'membership',
schema: 'club',
columns: {
group_id: { dataType: 'int', primaryKey: true},
user_id: { dataType: 'character varying', primaryKey: true},
}
}) What definition will allow for HackAdapting @spion mentioned d.ts, full text will follow below, if function define<T>(tabDef: TableDefinition<T>): Table & typeof tabDef.columns; with related definition interface TableDefinition<T> {
...
columns:T
} Note that columns become type loose, which should be explicitly tightened at definition of the table. Let's see how definition should look now: const t = sql.define({
...
columns: {
group_id: { dataType: 'int', ... } as Column<number>,
user_id: { dataType: 'character varying', ... } as Column<string>,
}
})
It works nicely, in that AnalysisStepping back, yes, this hack works for the purpose of strictly following columns, attached to table object. Yet, requirement of explicit cast seems ugly, initially. Asking:
Hacked definitions, first round:declare module 'sql' {
interface Dictionary<T> {
[key:string]:T;
}
interface OrderByValueNode {}
interface ColumnDefinition {
primaryKey?:boolean;
dataType?:string;
references?: {table:string; column: string}
notNull?:boolean
unique?:boolean
}
interface TableDefinition<T> {
name:string
columns:T
has?:Dictionary<{from:string; many?:boolean}>
}
interface QueryLike {
query:string;
values: any[]
text:string
}
interface SubQuery<T> {
select(node:Column<T>):SubQuery<T>
where(...nodes:any[]):SubQuery<T>
from(table:TableNode):SubQuery<T>
group(...nodes:any[]):SubQuery<T>
order(criteria:OrderByValueNode):SubQuery<T>
exists():BinaryNode
notExists(subQuery:SubQuery<any>):BinaryNode
}
interface Executable {
toQuery():QueryLike;
}
interface Queryable<T> {
where(...nodes:any[]):Query<T>
delete():ModifyingQuery
select<U>(...nodes:any[]):Query<U>
selectDeep<U>(...nodesOrTables:any[]):Query<U>
}
interface Query<T> extends Executable, Queryable<T> {
from(table:TableNode):Query<T>
update(o:Dictionary<any>):ModifyingQuery
update(o:{}):ModifyingQuery
group(...nodes:any[]):Query<T>
order(...criteria:OrderByValueNode[]):Query<T>
limit(l:number):Query<T>
offset(o:number):Query<T>
}
interface ModifyingQuery extends Executable {
returning<U>(...nodes:any[]):Query<U>
where(...nodes:any[]):ModifyingQuery
}
interface TableNode {
join(table:TableNode):JoinTableNode
leftJoin(table:TableNode):JoinTableNode
}
interface JoinTableNode extends TableNode {
on(filter:BinaryNode):TableNode
on(filter:string):TableNode
}
interface CreateQuery extends Executable {
ifNotExists():Executable
}
interface DropQuery extends Executable {
ifExists():Executable
}
interface Table extends TableNode, Queryable<any> {
create():CreateQuery
drop():DropQuery
as(name:string):Table
update(o:any):ModifyingQuery
insert<T>(row:T):ModifyingQuery
insert<T>(rows:T[]):ModifyingQuery
select<T>():Query<T>
select<U>(...nodes:any[]):Query<U>
from<U>(table:TableNode):Query<U>
star():Column<any>
subQuery<U>():SubQuery<U>
eventEmitter:{emit:(type:string, ...args:any[])=>void
on:(eventName:string, handler:Function)=>void}
columns:Column<any>[]
sql: SQL;
alter():AlterQuery<any>
}
interface AlterQuery<T> extends Executable {
addColumn(column:Column<any>): AlterQuery<T>;
addColumn(name: string, options:string): AlterQuery<T>;
dropColumn(column: Column<any>|string): AlterQuery<T>;
renameColumn(column: Column<any>, newColumn: Column<any>):AlterQuery<T>;
renameColumn(column: Column<any>, newName: string):AlterQuery<T>;
renameColumn(name: string, newName: string):AlterQuery<T>;
rename(newName: string): AlterQuery<T>
}
interface SQL {
functions: {
LOWER(c:Column<string>):Column<string>
}
}
interface BinaryNode {
and(node:BinaryNode):BinaryNode
or(node:BinaryNode):BinaryNode
}
interface Column<T> extends ColumnDefinition {
in(arr:T[]):BinaryNode
in(subQuery:SubQuery<T>):BinaryNode
notIn(arr:T[]):BinaryNode
equals(node:any):BinaryNode
notEquals(node:any):BinaryNode
gte(node:any):BinaryNode
lte(node:any):BinaryNode
gt(node:any):BinaryNode
lt(node:any):BinaryNode
like(str:string):BinaryNode
multiply:{
(node:Column<T>):Column<T>
(n:number):Column<number>
}
isNull():BinaryNode
isNotNull():BinaryNode
sum():Column<number>
count():Column<number>
count(name:string):Column<number>
distinct():Column<T>
as(name:string):Column<T>
ascending:OrderByValueNode
descending:OrderByValueNode
asc:OrderByValueNode
desc:OrderByValueNode
name: string;
table: Table;
}
function define<T>(tabDef: TableDefinition<T>): Table & typeof tabDef.columns;
} |
@3n-mb if you provide a defaultValue for the column definition, everything works, e.g. try const t = sql.define({
name: 'membership',
//schema: 'club',
columns: {
group_id: { dataType: 'int', primaryKey: true, defaultValue: 0},
user_id: { dataType: 'character varying', primaryKey: true, defaultValue: ''},
}
}) If you can't do that, you can provide a type parameter manually: const t2 = sql.define<'membership', {group_id: number, user_id: string}>({
name: 'membership',
//schema: 'club',
columns: {
group_id: { dataType: 'int', primaryKey: true},
user_id: { dataType: 'character varying', primaryKey: true},
}
}) Another alternative is to add sql.intColumn, sql.stringColumn etc functions that automatically assign the type to the column. That would look like this const t3 = sql.define({
name: 'membership',
//schema: 'club',
columns: {
group_id: sql.intColumn({ primaryKey: true})
user_id: sql.varcharColumn({ primaryKey: true}),
}
}) and the column types will be correct. |
@spion yes! this works. I still cannot completely grasp types' reshuffling you have in the code (line 28 and 221, taken together ?). Let's build on it! interface ColumnDefinition<Name extends string, Type> extends Named<Name> {
jsType: Type;
...
defaultValue?: Type
} And example becomes const t2 = sql.define({
name: 'membership',
//schema: 'club',
columns: {
group_id: { dataType: 'int', jsType: 1, ...},
user_id: { dataType: 'character varying', jsType: '', ...},
params: { dataType: 'jsonb', jsType: {} as object, ...},
} Third example with Hacked code with jsType, second round:declare module "sql" {
interface OrderByValueNode {}
interface Named<Name extends string> {
name?: Name;
}
interface ColumnDefinition<Name extends string, Type> extends Named<Name> {
jsType: Type;
primaryKey?:boolean;
dataType?:string;
references?: {table:string; column: string}
notNull?:boolean
unique?:boolean
defaultValue?: Type
}
interface TableDefinition<Name extends string, Row> {
name: Name
columns:{[CName in keyof Row]: ColumnDefinition<CName, Row[CName]>}
has?:{[key: string]:{from:string; many?:boolean}}
}
interface QueryLike {
query:string;
values: any[]
text:string
}
interface DatabaseConnection {
queryAsync<T>(query:string, ...params:any[]):Promise<{rowCount:number;rows:T[]}>
queryAsync<T>(query:QueryLike):Promise<{rowCount:number;rows:T[]}>
}
interface Transaction extends DatabaseConnection {
rollback():void
commitAsync():Promise<void>
}
interface Executable {
toQuery():QueryLike;
}
interface Queryable<T> {
where(...nodes:any[]):Query<T>
delete():ModifyingQuery
select<N1 extends string, T1>(n1: Column<N1, T1>):Query<T1>
select<N1 extends string, T1, N2 extends string, T2>(
n1: Column<N1, T1>,
n2: Column<N2, T2>):Query<{[N in N1]: T1} & {[N in N2]: T2}>
select<N1 extends string, T1, N2 extends string, T2, N3 extends string, T3>(
n1: Column<N1, T1>,
n2: Column<N2, T2>,
n3: Column<N3, T3>):Query<{[N in N1]: T1} & {[N in N2]: T2} & {[N in N3]: T3}>
select<U>(...nodesOrTables:any[]):Query<U>
selectDeep<N1 extends string, T1>(n1: Table<N1, T1>):Query<T1>
selectDeep<N1 extends string, T1, N2 extends string, T2>(
n1: Table<N1, T1>,
n2: Table<N2, T2>):Query<{[N in N1]: T1} & {[N in N2]: T2}>
selectDeep<N1 extends string, T1, N2 extends string, T2, N3 extends string, T3>(
n1: Table<N1, T1>,
n2: Table<N2, T2>,
n3: Table<N3, T3>):Query<{[N in N1]: T1} & {[N in N2]: T2} & {[N in N3]: T3}>
//selectDeep<U>(...nodesOrTables:any[]):Query<U>
}
interface Query<T> extends Executable, Queryable<T> {
from(table:TableNode):Query<T>
from(statement:string):Query<T>
update(o:{[key: string]:any}):ModifyingQuery
update(o:{}):ModifyingQuery
group(...nodes:any[]):Query<T>
order(...criteria:OrderByValueNode[]):Query<T>
limit(l:number):Query<T>
offset(o:number):Query<T>
}
interface SubQuery<T> {
select<Name>(node:Column<Name, T>):SubQuery<T>
select(...nodes: any[]):SubQuery<T>
where(...nodes:any[]):SubQuery<T>
from(table:TableNode):SubQuery<T>
from(statement:string):SubQuery<T>
group(...nodes:any[]):SubQuery<T>
order(criteria:OrderByValueNode):SubQuery<T>
exists():BinaryNode
notExists(): BinaryNode;
notExists(subQuery:SubQuery<any>):BinaryNode
}
interface ModifyingQuery extends Executable {
returning<U>(...nodes:any[]):Query<U>
where(...nodes:any[]):ModifyingQuery
}
interface TableNode {
join(table:TableNode):JoinTableNode
leftJoin(table:TableNode):JoinTableNode
}
interface JoinTableNode extends TableNode {
on(filter:BinaryNode):TableNode
on(filter:string):TableNode
}
interface CreateQuery extends Executable {
ifNotExists():Executable
}
interface DropQuery extends Executable {
ifExists():Executable
}
type Columns<T> = {
[Name in keyof T]: Column<Name, T[Name]>
}
type Table<Name extends string, T> = TableNode & Queryable<T> & Named<Name> & Columns<T> & {
create():CreateQuery
drop():DropQuery
as<OtherName extends string>(name:OtherName):Table<OtherName, T>
update(o:any):ModifyingQuery
insert(row:T):ModifyingQuery
insert(rows:T[]):ModifyingQuery
select():Query<T>
select<U>(...nodes:any[]):Query<U>
from<U>(table:TableNode):Query<U>
from<U>(statement:string):Query<U>
star():Column<void, void>
subQuery<U>():SubQuery<U>
eventEmitter:{emit:(type:string, ...args:any[])=>void
on:(eventName:string, handler:Function)=>void}
columns:Column<void, void>[]
sql: SQL;
alter():AlterQuery<T>;
indexes(): IndexQuery;
}
type Selectable<Name extends string, T> = Table<Name, T> | Column<Name, T>
interface AlterQuery<T> extends Executable {
addColumn(column:Column<any, any>): AlterQuery<T>;
addColumn(name: string, options:string): AlterQuery<T>;
dropColumn(column: Column<any, any>|string): AlterQuery<T>;
renameColumn(column: Column<any, any>, newColumn: Column<any, any>):AlterQuery<T>;
renameColumn(column: Column<any, any>, newName: string):AlterQuery<T>;
renameColumn(name: string, newName: string):AlterQuery<T>;
rename(newName: string): AlterQuery<T>
}
interface IndexQuery {
create(): IndexCreationQuery;
create(indexName: string): IndexCreationQuery;
drop(indexName: string): Executable;
drop(...columns: Column<any, any>[]): Executable
}
interface IndexCreationQuery extends Executable {
unique(): IndexCreationQuery;
using(name: string): IndexCreationQuery;
on(...columns: (Column<any, any>|OrderByValueNode)[]): IndexCreationQuery;
withParser(parserName: string): IndexCreationQuery;
fulltext(): IndexCreationQuery;
spatial(): IndexCreationQuery;
}
interface SQL {
functions: {
LOWER<Name>(c:Column<Name, string>):Column<Name, string>
}
}
interface BinaryNode {
and(node:BinaryNode):BinaryNode
or(node:BinaryNode):BinaryNode
}
interface Column<Name, T> {
name: Name
in(arr:T[]):BinaryNode
in(subQuery:SubQuery<T>):BinaryNode
notIn(arr:T[]):BinaryNode
equals(node:any):BinaryNode
notEquals(node:any):BinaryNode
gte(node:any):BinaryNode
lte(node:any):BinaryNode
gt(node:any):BinaryNode
lt(node:any):BinaryNode
like(str:string):BinaryNode
multiply:{
(node:Column<any, T>):Column<any, T>
(n:number):Column<any, number> //todo check column names
}
isNull():BinaryNode
isNotNull():BinaryNode
//todo check column names
sum():Column<any, number>
count():Column<any, number>
count(name:string):Column<any, number>
distinct():Column<Name, T>
as<OtherName>(name:OtherName):Column<OtherName, T>
ascending:OrderByValueNode
descending:OrderByValueNode
asc:OrderByValueNode
desc:OrderByValueNode
}
function define<Name extends string, T>(map:TableDefinition<Name, T>): Table<Name, T>;
} |
@spion is |
@3n-mb most of the "magic" is on line 21. For each field name in the type of the table row, create a column type based on the type of that field in the table row. Now the reason why it wont work without a default value is because TypeScript has to somehow infer what kind of type is the field - once it does that, it can "pull it in" from I don't know if its possible to put a type as a field value in the column definition. It might be, with more mapped types hackery, but I don't think it is... Lets try: type Constructor<T> = {
new(t:any):T
} We added a constructor type, now we can add another optional field in the definition that node-sql will never look at: export interface ColumnDefinition<Name extends string, Type> extends Named<Name> {
primaryKey?:boolean;
dataType?:string;
references?: {table:string; column: string}
notNull?:boolean
unique?:boolean
defaultValue?: Type
jsType?: Constructor<Type>
} Now we can use jsType instead: const t4 = sql.define({
name: 'membership',
//schema: 'club',
columns: {
group_id: {dataType: 'int', primaryKey: true, jsType: Number},
user_id: { dataType: 'character varying', primaryKey: true, jsType: String},
}
}) But you still need hackery for JSON objects with specific shapes. Lets try adding that too... From within the module: export function JSONObject<T>(o: {[K in keyof T]: Constructor<T[K]>}): Constructor<T> {
return o as any;
} Now we can trick typescript into realising whats the type of this JSON object: const t5 = sql.define({
name: 'membership',
//schema: 'club',
columns: {
group_id: {dataType: 'int', primaryKey: true, jsType: Number},
user_id: { dataType: 'character varying', primaryKey: true, jsType: String },
extras: {dataType: 'json', jsType: anydbSQL.JSONObject({field1: String, field2: Number}) }
}
}) Its not important what the function returns, its only there to placate TypeScript into accepting the type of the column. Yes, sql.intColumn and other types of columns would have to be added to node-sql. We could perhaps send a PR for it and if @brianc is okay with it he could merge it. They're just small things that would make it easier and more ergonomic to model the library with typescript... I mean the above hacks work too, they are just annoying and error prone (what if we use dataType int with jsType string? Chaos!)
One of these days, I'm hoping I'll have the chance to clean all this up and contribute proper typings... but right now I can't |
@spion thank you for this piece, it really is useful in the second form, i.e. I am now using it this way, love it so far, in comparison to other ... non-options, in comparison. For the future, I think that in column interface, things like |
@3n-mb sorry I started editing my original comment and came up with another hack that almost gets the definitions where you wanted them to be (the only caveat is you need to put the JSONObject function somewhere - probably not in the sql module but something like edit: actually individual types can be in a separate module called sql-types or something like that. They would generate column definitions with the appropriate I'm really excited to polish this up a little more... as soon as I find the chance! |
It would be really good if we can somehow "export" information about selected columns, to achieve typecheck for query result. |
@Strate let try a little hack to pick type for variable, into which result will be placed. interface MembershipRecord {
group_id: number;
user_id: string;
params?: object;
}
const t2 = sql.define<'membership', MembershipRecord>({
name: 'membership',
schema: 'club',
columns: {
group_id: { dataType: 'int', ...},
user_id: { dataType: 'character varying', ...},
params: { dataType: 'jsonb', ...},
} then, let's take a particular query we want to make: const q = t2
.select(t2.user_id, t2.params)
.where(t2.group_id.equals(-1)); before we jump interface Query<T> {
...
resultType: T;
} expanding our example: const q = t2
.select(t2.user_id, t2.params)
.where(t2.group_id.equals(-1));
let resultRow: typeof q.resultType; Try to use it, share your opinion. Note that I have introduced record's type in the very front, and seeded with its type into |
Suggestion for types for column methodsAs for types for interface Column<Name, T> {
...
equals(node: T|Column<any, T>):BinaryNode
notEquals(node: T|Column<any, T>):BinaryNode
gte(node: T|Column<any, T>):BinaryNode
lte(node: T|Column<any, T>):BinaryNode
gt(node:T|Column<any, T>):BinaryNode
lt(node: T|Column<any, T>):BinaryNode
...
} It works for me, so far. @spion do you think there may be other unforeseen things here? Or can this be done better? selectDeep question@spion is Definitions, round three, adding some more node-sql's own thingsdeclare module "sql" {
interface OrderByValueNode {}
interface Named<Name extends string> {
name?: Name;
}
interface ColumnDefinition<Name extends string, Type> extends Named<Name> {
jsType?: Type;
dataType: string;
primaryKey?: boolean;
references?: {
table:string;
column: string;
onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default';
onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default';
};
notNull?: boolean;
unique?: boolean;
defaultValue?: Type;
}
interface TableDefinition<Name extends string, Row> {
name: Name;
schema: string;
columns: {[CName in keyof Row]: ColumnDefinition<CName, Row[CName]>};
isTemporary?: boolean;
foreignKeys?: {
table: string,
columns: string[],
refColumns: string[]
}
}
interface QueryLike {
values: any[]
text:string
}
interface Executable {
toQuery():QueryLike;
}
interface Queryable<T> {
where(...nodes:any[]):Query<T>
delete():ModifyingQuery
select<N1 extends string, T1>(n1: Column<N1, T1>):Query<T1>
select<N1 extends string, T1, N2 extends string, T2>(
n1: Column<N1, T1>,
n2: Column<N2, T2>):Query<{[N in N1]: T1} & {[N in N2]: T2}>
select<N1 extends string, T1, N2 extends string, T2, N3 extends string, T3>(
n1: Column<N1, T1>,
n2: Column<N2, T2>,
n3: Column<N3, T3>):Query<{[N in N1]: T1} & {[N in N2]: T2} & {[N in N3]: T3}>
select<U>(...nodesOrTables:any[]):Query<U>
selectDeep<N1 extends string, T1>(n1: Table<N1, T1>):Query<T1>
selectDeep<N1 extends string, T1, N2 extends string, T2>(
n1: Table<N1, T1>,
n2: Table<N2, T2>):Query<{[N in N1]: T1} & {[N in N2]: T2}>
selectDeep<N1 extends string, T1, N2 extends string, T2, N3 extends string, T3>(
n1: Table<N1, T1>,
n2: Table<N2, T2>,
n3: Table<N3, T3>):Query<{[N in N1]: T1} & {[N in N2]: T2} & {[N in N3]: T3}>
selectDeep<U>(...nodesOrTables:any[]):Query<U>
}
interface Query<T> extends Executable, Queryable<T> {
resultType: T;
from(table:TableNode):Query<T>
from(statement:string):Query<T>
update(o:{[key: string]:any}):ModifyingQuery
update(o:{}):ModifyingQuery
group(...nodes:any[]):Query<T>
order(...criteria:OrderByValueNode[]):Query<T>
limit(l:number):Query<T>
offset(o:number):Query<T>
}
interface SubQuery<T> {
select<Name>(node:Column<Name, T>):SubQuery<T>
select(...nodes: any[]):SubQuery<T>
where(...nodes:any[]):SubQuery<T>
from(table:TableNode):SubQuery<T>
from(statement:string):SubQuery<T>
group(...nodes:any[]):SubQuery<T>
order(criteria:OrderByValueNode):SubQuery<T>
exists():BinaryNode
notExists(): BinaryNode;
notExists(subQuery:SubQuery<any>):BinaryNode
}
interface ModifyingQuery extends Executable {
returning<U>(...nodes:any[]):Query<U>
where(...nodes:any[]):ModifyingQuery
}
interface TableNode {
join(table:TableNode):JoinTableNode
leftJoin(table:TableNode):JoinTableNode
}
interface JoinTableNode extends TableNode {
on(filter:BinaryNode):TableNode
on(filter:string):TableNode
}
interface CreateQuery extends Executable {
ifNotExists():Executable
}
interface DropQuery extends Executable {
ifExists():Executable
}
type Columns<T> = {
[Name in keyof T]: Column<Name, T[Name]>
}
type Table<Name extends string, T> = TableNode & Queryable<T> & Named<Name> & Columns<T> & {
getName(): string;
getSchema(): string;
create():CreateQuery
drop():DropQuery
as<OtherName extends string>(name:OtherName):Table<OtherName, T>
update(o:any):ModifyingQuery
insert(row:T):ModifyingQuery
insert(rows:T[]):ModifyingQuery
select():Query<T>
select<U>(...nodes:any[]):Query<U>
from<U>(table:TableNode):Query<U>
from<U>(statement:string):Query<U>
star():Column<void, void>
subQuery<U>():SubQuery<U>
columns:Column<void, void>[]
sql: SQL;
alter():AlterQuery<T>;
indexes(): IndexQuery;
}
type Selectable<Name extends string, T> = Table<Name, T> | Column<Name, T>
interface AlterQuery<T> extends Executable {
addColumn(column:Column<any, any>): AlterQuery<T>;
addColumn(name: string, options:string): AlterQuery<T>;
dropColumn(column: Column<any, any>|string): AlterQuery<T>;
renameColumn(column: Column<any, any>, newColumn: Column<any, any>):AlterQuery<T>;
renameColumn(column: Column<any, any>, newName: string):AlterQuery<T>;
renameColumn(name: string, newName: string):AlterQuery<T>;
rename(newName: string): AlterQuery<T>
}
interface IndexQuery {
create(): IndexCreationQuery;
create(indexName: string): IndexCreationQuery;
drop(indexName: string): Executable;
drop(...columns: Column<any, any>[]): Executable
}
interface IndexCreationQuery extends Executable {
unique(): IndexCreationQuery;
using(name: string): IndexCreationQuery;
on(...columns: (Column<any, any>|OrderByValueNode)[]): IndexCreationQuery;
withParser(parserName: string): IndexCreationQuery;
fulltext(): IndexCreationQuery;
spatial(): IndexCreationQuery;
}
interface SQL {
functions: {
LOWER<Name>(c:Column<Name, string>):Column<Name, string>
}
}
interface BinaryNode {
and(node:BinaryNode):BinaryNode
or(node:BinaryNode):BinaryNode
}
interface Column<Name, T> {
name: Name
in(arr:T[]):BinaryNode
in(subQuery:SubQuery<T>):BinaryNode
notIn(arr:T[]):BinaryNode
equals(node: T|Column<any, T>):BinaryNode
notEquals(node: T|Column<any, T>):BinaryNode
gte(node: T|Column<any, T>):BinaryNode
lte(node: T|Column<any, T>):BinaryNode
gt(node:T|Column<any, T>):BinaryNode
lt(node: T|Column<any, T>):BinaryNode
like(str:string):BinaryNode
multiply:{
(node:Column<any, T>):Column<any, T>
(n:number):Column<any, number> //todo check column names
}
isNull():BinaryNode
isNotNull():BinaryNode
//todo check column names
sum():Column<any, number>
count():Column<any, number>
count(name:string):Column<any, number>
distinct():Column<Name, T>
as<OtherName>(name:OtherName):Column<OtherName, T>
ascending:OrderByValueNode
descending:OrderByValueNode
asc:OrderByValueNode
desc:OrderByValueNode
}
function define<Name extends string, T>(map:TableDefinition<Name, T>): Table<Name, T>;
} |
Yup, regarding The same corrections need to be done for Another thing,
|
Adding types to updateSo far, in SuggestionLet's make the following type: type PartialRecord<T> = {
[Name in keyof T]?: T[Name];
} (note question mark for making it partial) type Table<Name extends string, T> = TableNode & Queryable<T> & Named<Name> & Columns<T> & {
...
update(o: PartialRecord<T>): ModifyingQuery;
...
} Sample useWith this modification, given sample table: interface MembershipRecord {
group_id: number;
user_id: string;
params?: object;
}
const t2 = sql.define<'membership', MembershipRecord>({
name: 'membership',
schema: 'club',
columns: {
group_id: { dataType: 'int', ...},
user_id: { dataType: 'character varying', ...},
params: { dataType: 'jsonb', ...},
} typing checks what one puts in update object both for keys and value types: t2.update({ group_id: 9 }) ... // ok
t2.update({ groupId: 9 }) ... // not ok
t2.update({ group_id: '9' }) ... // not ok Writing an update also becomes |
@spion have you added |
We have interface Queryable<T> {
...
select(star: Column<void, void>): Query<T>;
...
} for those not uncommon times when we select star, like Works for me. Can it create any confusion later, or is it ok? |
+1 on the update update 😀 Yes I think you can get away w/o
type Brand<U> = { ' kind': U };
type AllColumns = Brand<'AllColumns'>
// the new definition of star()
type Table<T> = ... {
...
star():AllColumns
...
} |
Adding column names for aggregate standard functionsIt touches interface Column<Name, T> {
...
sum(): Column<'sum', number>;
count(): Column<'count', number>;
...
} Judging from experience with |
@spion I do not see any examples in node-sql for count(name:string): Column<any, number>; with argument name. Is it anydb's? |
It looks like count's default column name is "tablename_count" http://node-sql-examples.github.io/#dialect=pg&query=count Which is not possible to model with TS at the moment 😢 |
Have an update in restricting names of columns, named in interface TableDefinition<Name extends string, Row> {
name: Name;
schema: string;
columns: {[CName in keyof Row]: ColumnDefinition<CName, Row[CName]>};
isTemporary?: boolean;
foreignKeys?: {
table: string,
columns: (keyof Row)[],
refColumns: string[],
onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default';
onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default';
}
} When indicating a |
@3n-mb want to submit a PR to DefinitelyTyped or here or create a new repo with the definitions? |
@spion ya. I haven't found any burning things. Except that other module functions can be added, as well comments. |
Thanks for your work! It think there is a missing function:
And also:
Shall I proceed with a pull request for this? |
@aliem yes, please. Do a PR. |
I have a couple more things to add:
|
@aliem
To me this trade off feels like a part of whole deal of typescript helping to deal with sql tables. |
@aliem about enforcing not-null.
|
I agree Using a const t = db.Actions;
const f = db.Functions;
return t
.insert({
id: null,
created_at: null,
updated_at: null,
payload: opts.payload,
workflow_id: opts.workflow_id,
function_id: f
.subQuery()
.select(f.id)
.where({ uuid: opts.function_uuid }),
})
.returning(t.id); |
I have created a PR that adds the onConflict clause for Postgresql to the type definition. |
Please also merge #376 I'm still searching for documentation on the still non-optional but unnecessary schema attribute! |
Was able to find https://github.com/brianc/node-sql/blob/master/test/dialects/schema-tests.js as documentation for the schema attribute. It's definitely not non-optional. import { define } from 'sql';
export const user = define(<any>{ // <any> disables ts typecheck
name: 'user',
columns: ['id', 'name'],
}); |
@Justus-Maier import { define } from 'sql';
export const user = define({
name: 'user',
schema: undefined as any, // ugliness points to shortcoming in def, while undefined gives you needed js
columns: ['id', 'name'],
}); This way you don't throw types (baby), while clearly identifying a shortcoming in type definition (bathing water). |
@3n-mb I'd prefer your workaround if I wouldn't be running into the next type error:
Is there something more I need to know to use typescript with node-sql? This continues, user.id & user.name from the example are not recognised, and the new workaround looks like this: ...
export const user = <any>define(<any>{ // <any> disables ts typecheck
... Maybe I should prefer :/ const sql = require("sql") as any; |
@Justus-Maier error says that you don't give type to an sql column. Can db create column without being told its type? No. And here you found it before talking to sql db. If By the way, I remember there was something security-related around |
@3n-mb so this means I have to declare columns differently? Can you link an example please? How is schema relevant if not using an OracleDB? |
@Justus-Maier roll back, and start with postgres documentation. Then you'll see meaning in all of those typed fields: No, there is no shorter way. 😢 |
@Justus-Maier its not the schema. The type definition version doesn't support the array of strings syntax for fields. Instead for each field you need to use the object notation specifying what dataType / jsType it has. |
Sorry for spamming this thread, but it seems documentation is missing for this. Also the schema is not relevant for my usecase. The database I am accessing exists in mssql and postgres variants and therefor has no postgres schemas due to incompatbility. Bridging those variants is the reason why I'm trying to use this SQL query string generator. from the Postgres documentation
|
Improving on my own example.. import { define, ColumnDefinition } from 'sql';
export const user = define({
name: 'user',
schema: undefined as any, // ugliness points to shortcoming in def, while undefined gives you needed js
columns: {
id: <ColumnDefinition<'id', String>>{
name: 'id'
},
name: <ColumnDefinition<'name', String>>{
name: 'name'
}
}
}); Looks pretty verbose but it works. Please add something like this to the readme. |
@Justus-Maier Use Indeed, this is not documented anywhere. Sorry about that. I'm hoping that in the following months I'll manage to find some time to contribute to the type support code and make it friendly for users (docs, examples, tests etc) |
I have been using a few helpers to generate definitions, take a look
here: https://github.com/zenlor/node-sql-types it is probably what you
are looking for, but it hides implementation details under another
layer increasing overall complexity.
It's not complete and it's very opinionated given my use case.
The best integration I could try is still `anydb-sql` but it's in need
of an update on both `node-sql` and the definitions.
I have been thinkering lately with a logicless model implementation but
typescript's type inference, sometimes, is far too strict and difficult
to implement without macros (it would be so much easier with reader
macros)
|
Still not solved? Oh my. What if want to generate an insert query for some generic table. How do I safely type that, when I do not even know what the types of those tables are? |
@jayarjo what precisely do you mean?
|
@3n-mb I cannot finish definition of the table. I do not know beforehand what the keys of the object will be. I just want to generate the insert query. Suggestions? |
@jayarjo can you flesh it this out a bit more in code snippets of how you want to see things. |
@3n-mb I need to be able to generate an sql for insert (or any other type) query out of totally generic tableName and array of column names. Something like:
where My impression is that it is not possible, at the moment. |
@jayarjo Have you tried the following partial release of type strictness, assuming also that tableName and columns are not known at time of ts' compilation: const table = define<'any-name', any>{
name: tableName as 'any-name',
columns: columns
} Note that type constraint on table name value is simply enforcing strictness (it saved me once already 😄 ). But putting |
@3n-mb not sure what that does, but it didn't work. |
@jayarjo give more context. How is table consumed/used? What doesn't work precisely? |
@jayarjo |
It's too much hassle, really. I simply wrote my own query generator. Five lines - that's it. You guys overcomplicate. |
@jayarjo I look at types not as a hassle, but as a fun exercise of using very flexible Typescript type system. |
@3n-mb I cannot provide my code, sorry. But what I basically did is that I wrote a generic data seeder using pg-structure and faker that generates arbitrary amount of fake seed data, taking into account structure of the given database, types of the columns, format, length, etc. It is actually interesting if there's anything you can recommend for such use case? |
|
@3n-mb no man, you over complicate. And apparently not only in tech. I described my case in quite a detail. Not sure what to add. I doubt that implementation will help to add any context. |
I really want to use this library, but i can't know how work with it. I trying to do a
d.ts
file to improve it use but i need some help to know what do with each method and what it receive as parameters. if somebody can help me, i'll really apreciate it.The text was updated successfully, but these errors were encountered: