Skip to content

Commit

Permalink
Bugfix/default values incorrectly seralized (#20)
Browse files Browse the repository at this point in the history
* fix

* fixes

* nullable type default value fix
  • Loading branch information
rohit-kadhe authored Apr 3, 2024
1 parent 04e17c1 commit 17b8eb3
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 41 deletions.
31 changes: 20 additions & 11 deletions core/clickhouse_schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { type ChDataType } from '@clickhouse-schema-data-types/index'

export interface SchemaValue { type: ChDataType }
type IsType<T, U> = T extends U ? true : false

// Distributive conditional type that checks if T is of any type in union U
type OmitKeysOfTypes<T extends ChSchemaDefinition, U> = {
[K in keyof T as IsType< T[K]['type']['typeStr'], U> extends true ? never : K]: T[K]
}

export interface SchemaValue {
type: ChDataType
}
export type ChSchemaDefinition = Record<string, SchemaValue>
/**
* ChSchemaOptions is used to define the options for a clickhouse table schema.
Expand All @@ -13,20 +22,19 @@ export type ChSchemaDefinition = Record<string, SchemaValue>
* @param engine is the engine to use for the table, default is MergeTree()
* @param additional_options is an string array of options that are appended to the end of the create table query
*/
export interface ChSchemaOptions<T> {
export interface ChSchemaOptions<T extends ChSchemaDefinition> {
database?: string
table_name: string
on_cluster?: string
primary_key?: keyof T
order_by?: keyof T
primary_key?: keyof OmitKeysOfTypes<T, 'Object(\'JSON\')' >
order_by?: keyof OmitKeysOfTypes<T, 'Object(\'JSON\')' >
engine?: string
additional_options?: string[]
}

/**
* IClickhouseSchema is an interface that represents a clickhouse schema.
*/
interface IClickhouseSchema<T> {
interface IClickhouseSchema<T extends ChSchemaDefinition> {
GetOptions: () => ChSchemaOptions<T>
GetCreateTableQuery: () => string
GetCreateTableQueryAsList: () => string[]
Expand Down Expand Up @@ -58,10 +66,11 @@ export class ClickhouseSchema<SchemaDefinition extends ChSchemaDefinition> imple
const columns = Object.entries(this.schema as ChSchemaDefinition)
.map(([name, field]) => {
// Check if default is defined and a string, add single quotes; otherwise, just use the value
const defaultValue = field.type.default !== undefined
? (typeof field.type.default === 'string' ? `'${field.type.default}'` : field.type.default)
: ''
return `${name} ${field.type}${field.type.default !== undefined ? ` DEFAULT ${defaultValue}` : ''}`
let res = `${name} ${field.type}${field.type.default !== undefined ? ` DEFAULT ${field.type.typeStr === 'Object(\'JSON\')' ? `'${JSON.stringify(field.type.default)}'` : `${JSON.stringify(field.type.default)}`}` : ''}`

Check warning on line 69 in core/clickhouse_schema.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
if (field.type.typeStr !== 'Object(\'JSON\')') {
res = res.replace(/"/g, "'")
}
return res
}
)
.join(',\n')
Expand All @@ -79,7 +88,7 @@ export class ClickhouseSchema<SchemaDefinition extends ChSchemaDefinition> imple
additionalOptions
].filter(part => part.trim().length > 0).join('\n')

return createTableQuery
return `${createTableQuery};`
}

/**
Expand Down
4 changes: 2 additions & 2 deletions core/infer_schema_type.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { type ClickhouseSchema } from '@clickhouse-schema-core/clickhouse_schema'
import { type ChSchemaDefinition, type ClickhouseSchema } from '@clickhouse-schema-core/clickhouse_schema'
import { type ChDataType } from '@clickhouse-schema-data-types/index'

/** Infer is a type that takes a ChDataType and returns the typescript that it represents */
type Infer<T extends ChDataType> = T['typeScriptType']

/** InferSchemaClickhouseSchemaType is a type that takes a ClickhouseSchema and returns the typescript that it represents */
export type InferClickhouseSchemaType<T extends ClickhouseSchema<any>> = { [K in keyof T['schema']]: Infer<T['schema'][K]['type']> }
export type InferClickhouseSchemaType<T extends ClickhouseSchema<ChSchemaDefinition>> = { [K in keyof T['schema']]: Infer<T['schema'][K]['type']> }
4 changes: 2 additions & 2 deletions data_types/ch_json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { type ChDataType } from '@clickhouse-schema-data-types/index'
* ChJSON is a class that represents a Clickhouse JSON data type
*/
export class ChJSON<T extends ChSchemaDefinition> implements ChDataType {
readonly typeStr: 'JSON'
readonly typeStr: 'Object(\'JSON\')'
readonly innerType: T
readonly typeScriptType!: { [K in keyof T]: T[K]['type']['typeScriptType'] }
readonly default?: { [K in keyof T]: T[K]['type']['typeScriptType'] }

constructor (innerType: T, defaultValue?: { [K in keyof T]: T[K]['type']['typeScriptType'] }) {
this.typeStr = 'JSON'
this.typeStr = 'Object(\'JSON\')'
this.innerType = innerType
this.default = defaultValue
}
Expand Down
6 changes: 3 additions & 3 deletions data_types/ch_nullable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { type ChPrimitiveType, type ChDataType } from '@clickhouse-schema-data-t
export class ChNullable<T extends ChPrimitiveType & ChDataType> implements ChDataType {
readonly typeStr
readonly innerType: T
readonly typeScriptType!: T['typeScriptType']
readonly default?: T['typeScriptType']
readonly typeScriptType!: T['typeScriptType'] | null
readonly default?: T['typeScriptType'] | null

constructor (t: T, defaultVal?: T['typeScriptType']) {
constructor (t: T, defaultVal?: T['typeScriptType'] | null) {
this.innerType = t
this.typeStr = `Nullable(${this.innerType.toString()})`
this.default = defaultVal
Expand Down
48 changes: 26 additions & 22 deletions tests/unit/clickhouse_schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ describe('ClickhouseSchema Tests', () => {
it('should correctly store schema definitions and options', () => {
const schemaDefinition = {
id: { type: ClickhouseTypes.CHUInt128() },

ch_json: { type: ClickhouseTypes.CHJSON({ k: { type: ClickhouseTypes.CHString() }, arr: { type: ClickhouseTypes.CHArray(ClickhouseTypes.CHJSON({ nested: { type: ClickhouseTypes.CHString() } })) } }) }
}
const options: ChSchemaOptions<typeof schemaDefinition> = {
primary_key: 'id',
order_by: 'id',
table_name: 'users_table',
engine: 'ReplicatedMergeTree()'
}
Expand Down Expand Up @@ -51,7 +51,7 @@ describe('ClickhouseSchema Tests', () => {
}

const schema = new ClickhouseSchema(schemaDefinition, options)
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String,\nemail String,\nage UInt8\n)\nENGINE = MergeTree()\nPRIMARY KEY id'
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String,\nemail String,\nage UInt8\n)\nENGINE = MergeTree()\nPRIMARY KEY id;'
const query = schema.GetCreateTableQuery()
expect(query).toEqual(expectedQuery)
})
Expand All @@ -69,7 +69,7 @@ describe('ClickhouseSchema Tests', () => {
}

const schema = new ClickhouseSchema(schemaDefinition, options)
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String DEFAULT \'[email protected]\',\nage UInt8\n)\nENGINE = MergeTree()\nPRIMARY KEY id'
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String DEFAULT \'[email protected]\',\nage UInt8\n)\nENGINE = MergeTree()\nPRIMARY KEY id;'
const query = schema.GetCreateTableQuery()
expect(query).toEqual(expectedQuery)
})
Expand All @@ -87,7 +87,7 @@ describe('ClickhouseSchema Tests', () => {
additional_options: ['COMMENT \'This table provides user details\'']
}
const schema = new ClickhouseSchema(schemaDefinition, options)
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String DEFAULT \'[email protected]\',\nage UInt8 DEFAULT 18\n)\nENGINE = MergeTree()\nPRIMARY KEY id\nCOMMENT \'This table provides user details\''
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String DEFAULT \'[email protected]\',\nage UInt8 DEFAULT 18\n)\nENGINE = MergeTree()\nPRIMARY KEY id\nCOMMENT \'This table provides user details\';'
const query = schema.GetCreateTableQuery()
expect(query).toEqual(expectedQuery)
})
Expand All @@ -103,7 +103,7 @@ describe('ClickhouseSchema Tests', () => {
order_by: 'id'
}
const schema = new ClickhouseSchema(schemaDefinition, options)
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree()\nORDER BY id'
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree()\nORDER BY id;'
const query = schema.GetCreateTableQuery()
expect(query).toEqual(expectedQuery)
})
Expand All @@ -120,7 +120,7 @@ describe('ClickhouseSchema Tests', () => {
on_cluster: 'users_cluster'
}
const schema = new ClickhouseSchema(schemaDefinition, options)
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table ON CLUSTER users_cluster\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree()\nPRIMARY KEY id'
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table ON CLUSTER users_cluster\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree()\nPRIMARY KEY id;'
const query = schema.GetCreateTableQuery()
expect(query).toEqual(expectedQuery)
})
Expand All @@ -135,7 +135,7 @@ describe('ClickhouseSchema Tests', () => {
primary_key: 'id',
engine: 'ReplicatedMergeTree()'
})
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = ReplicatedMergeTree()\nPRIMARY KEY id'
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = ReplicatedMergeTree()\nPRIMARY KEY id;'
const query = schema.GetCreateTableQuery()
expect(query).toEqual(expectedQuery)
})
Expand All @@ -152,7 +152,7 @@ describe('ClickhouseSchema Tests', () => {
primary_key: 'id'
}
const schema = new ClickhouseSchema(schemaDefinition, options)
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_db.users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree()\nPRIMARY KEY id'
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_db.users_table\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree()\nPRIMARY KEY id;'
const query = schema.GetCreateTableQuery()
expect(query).toEqual(expectedQuery)
})
Expand Down Expand Up @@ -181,7 +181,7 @@ describe('ClickhouseSchema Tests', () => {
'ENGINE = MergeTree()',
'ORDER BY id',
'PRIMARY KEY id',
'COMMENT \'This table provides user details\''
'COMMENT \'This table provides user details\';'
]
const query = schema.GetCreateTableQueryAsList()
expect(query).toEqual(expectedQuery)
Expand All @@ -201,26 +201,30 @@ describe('ClickhouseSchema Tests', () => {
additional_options: ['COMMENT \'This table provides user details\'']
}
const schema = new ClickhouseSchema(schemaDefinition, options)
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table ON CLUSTER users_cluster\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree()\nORDER BY id\nPRIMARY KEY id\nCOMMENT \'This table provides user details\''
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table ON CLUSTER users_cluster\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree()\nORDER BY id\nPRIMARY KEY id\nCOMMENT \'This table provides user details\';'
// eslint-disable-next-line @typescript-eslint/no-base-to-string
const query = schema.toString()

expect(query).toEqual(expectedQuery)
})

it('should not throw if on_cluster is specified but primary_key or order_by is not', () => {
const schemaDefinition = {
id: { type: ClickhouseTypes.CHUUID() },
name: { type: ClickhouseTypes.CHString('John Doe') },
email: { type: ClickhouseTypes.CHString() }
try {
const schemaDefinition = {
id: { type: ClickhouseTypes.CHUUID() },
name: { type: ClickhouseTypes.CHString('John Doe') },
email: { type: ClickhouseTypes.CHString() }
}
const options: ChSchemaOptions<typeof schemaDefinition> = {
table_name: 'users_table',
on_cluster: 'users_cluster'
}
const schema = new ClickhouseSchema(schemaDefinition, options)
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table ON CLUSTER users_cluster\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree();'
const query = schema.GetCreateTableQuery()
expect(query).toEqual(expectedQuery)
} catch (e) {
console.log(e, 'hereeeeeee')
}
const options: ChSchemaOptions<typeof schemaDefinition> = {
table_name: 'users_table',
on_cluster: 'users_cluster'
}
const schema = new ClickhouseSchema(schemaDefinition, options)
const expectedQuery = 'CREATE TABLE IF NOT EXISTS users_table ON CLUSTER users_cluster\n(\nid UUID,\nname String DEFAULT \'John Doe\',\nemail String\n)\nENGINE = MergeTree()'
const query = schema.GetCreateTableQuery()
expect(query).toEqual(expectedQuery)
})
})
2 changes: 1 addition & 1 deletion tests/unit/data_types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('Data Types Tests', () => {
fixedString: { type: ClickhouseTypes.CHFixedString(10) },
json: { type: ClickhouseTypes.CHJSON({ k: { type: ClickhouseTypes.CHString() } }) }
}, { array: [''], dateTime: new Date(), dateTime64: new Date(), enum: 'DELETE', fixedString: '', json: { k: '' } })
expect(json.toString()).toEqual('JSON')
expect(json.toString()).toEqual('Object(\'JSON\')')
})

it('should correctly create a enum data type with the correct typeStr', () => {
Expand Down

1 comment on commit 17b8eb3

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 100% 270/270
🟢 Branches 96% 24/25
🟢 Functions 100% 98/98
🟢 Lines 100% 206/206

Test suite run success

25 tests passing in 2 suites.

Report generated by 🧪jest coverage report action from 17b8eb3

Please sign in to comment.