| 
									
										
										
										
											2022-03-14 14:29:23 +01:00
										 |  |  | import * as plugins from './smartclickhouse.plugins.js'; | 
					
						
							|  |  |  | import { SmartClickHouseDb } from './smartclickhouse.classes.smartclickhouse.js'; | 
					
						
							| 
									
										
										
										
											2022-03-02 16:35:20 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 15:12:51 +01:00
										 |  |  | export type TClickhouseColumnDataType = 'String' | "DateTime64(3, 'Europe/Berlin')" | 'Float64' | 'Array(String)' | 'Array(Float64)'; | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  | export interface IColumnInfo { | 
					
						
							|  |  |  |   database: string; | 
					
						
							|  |  |  |   table: string; | 
					
						
							|  |  |  |   name: string; | 
					
						
							|  |  |  |   type: TClickhouseColumnDataType; | 
					
						
							|  |  |  |   position: string; | 
					
						
							|  |  |  |   default_kind: string; | 
					
						
							|  |  |  |   default_expression: string; | 
					
						
							|  |  |  |   data_compressed_bytes: string; | 
					
						
							|  |  |  |   data_uncompressed_bytes: string; | 
					
						
							|  |  |  |   marks_bytes: string; | 
					
						
							|  |  |  |   comment: string; | 
					
						
							|  |  |  |   is_in_partition_key: 0 | 1; | 
					
						
							|  |  |  |   is_in_sorting_key: 0 | 1; | 
					
						
							|  |  |  |   is_in_primary_key: 0 | 1; | 
					
						
							|  |  |  |   is_in_sampling_key: 0 | 1; | 
					
						
							|  |  |  |   compression_codec: string; | 
					
						
							|  |  |  |   character_octet_length: null; | 
					
						
							|  |  |  |   numeric_precision: null; | 
					
						
							|  |  |  |   numeric_precision_radix: null; | 
					
						
							|  |  |  |   numeric_scale: null; | 
					
						
							|  |  |  |   datetime_precision: '3'; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  | export interface ITimeDataTableOptions { | 
					
						
							|  |  |  |   tableName: string; | 
					
						
							|  |  |  |   retainDataForDays: number; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-02 16:35:20 +01:00
										 |  |  | export class TimeDataTable { | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |   public static async getTable(smartClickHouseDbRefArg: SmartClickHouseDb, tableNameArg: string) { | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |     const newTable = new TimeDataTable(smartClickHouseDbRefArg, { | 
					
						
							|  |  |  |       tableName: tableNameArg, | 
					
						
							|  |  |  |       retainDataForDays: 30 | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-03-02 16:35:20 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // create table in clickhouse
 | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |     await smartClickHouseDbRefArg.clickhouseClient | 
					
						
							| 
									
										
										
										
											2022-03-08 18:54:10 +01:00
										 |  |  |       .queryPromise(`
 | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |         CREATE TABLE IF NOT EXISTS ${newTable.options.tableName} ( | 
					
						
							| 
									
										
										
										
											2022-03-08 18:54:10 +01:00
										 |  |  |           timestamp DateTime64(3, 'Europe/Berlin'), | 
					
						
							|  |  |  |           message String | 
					
						
							| 
									
										
										
										
											2022-03-14 13:52:42 +01:00
										 |  |  |         ) ENGINE=MergeTree() ORDER BY timestamp`);
 | 
					
						
							| 
									
										
										
										
											2022-03-08 18:54:10 +01:00
										 |  |  |      | 
					
						
							|  |  |  |     // lets adjust the TTL
 | 
					
						
							|  |  |  |     await smartClickHouseDbRefArg.clickhouseClient | 
					
						
							|  |  |  |       .queryPromise(`
 | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |         ALTER TABLE ${newTable.options.tableName} MODIFY TTL toDateTime(timestamp) + INTERVAL ${newTable.options.retainDataForDays} DAY | 
					
						
							| 
									
										
										
										
											2022-03-08 18:54:10 +01:00
										 |  |  |       `);
 | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     await newTable.updateColumns(); | 
					
						
							|  |  |  |     console.log(`=======================`) | 
					
						
							|  |  |  |     console.log( | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |       `table with name "${newTable.options.tableName}" in databse ${newTable.smartClickHouseDbRef.options.database} has the following columns:` | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |     ); | 
					
						
							|  |  |  |     for (const column of newTable.columns) { | 
					
						
							|  |  |  |       console.log(`>> ${column.name}: ${column.type}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     console.log('^^^^^^^^^^^^^^\n'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-02 16:44:09 +01:00
										 |  |  |     return newTable; | 
					
						
							| 
									
										
										
										
											2022-03-02 16:35:20 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // INSTANCE
 | 
					
						
							| 
									
										
										
										
											2022-07-27 22:42:08 +02:00
										 |  |  |   public healingDeferred: plugins.smartpromise.Deferred<any>; | 
					
						
							| 
									
										
										
										
											2022-03-02 16:35:20 +01:00
										 |  |  |   public smartClickHouseDbRef: SmartClickHouseDb; | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |   public options: ITimeDataTableOptions; | 
					
						
							| 
									
										
										
										
											2022-03-02 16:44:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |   constructor(smartClickHouseDbRefArg: SmartClickHouseDb, optionsArg: ITimeDataTableOptions) { | 
					
						
							| 
									
										
										
										
											2022-03-02 16:35:20 +01:00
										 |  |  |     this.smartClickHouseDbRef = smartClickHouseDbRefArg; | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |     this.options = optionsArg; | 
					
						
							| 
									
										
										
										
											2022-03-02 16:35:20 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-03-02 16:44:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |   public columns: IColumnInfo[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * updates the columns | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   public async updateColumns() { | 
					
						
							|  |  |  |     this.columns = await this.smartClickHouseDbRef.clickhouseClient.queryPromise(`
 | 
					
						
							|  |  |  |       SELECT * FROM system.columns | 
					
						
							|  |  |  |       WHERE database LIKE '${this.smartClickHouseDbRef.options.database}' | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |       AND table LIKE '${this.options.tableName}' | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |     `);
 | 
					
						
							|  |  |  |     return this.columns; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-02 16:44:09 +01:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * stores a json and tries to map it to the nested syntax | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |   public async addData(dataArg: any) { | 
					
						
							| 
									
										
										
										
											2022-07-27 22:42:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |     // the storageJson
 | 
					
						
							|  |  |  |     let storageJson: { [key: string]: any } = {}; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // helper stuff
 | 
					
						
							| 
									
										
										
										
											2022-03-08 15:12:51 +01:00
										 |  |  |      | 
					
						
							|  |  |  |     const getClickhouseTypeForValue = (valueArg: any): TClickhouseColumnDataType => { | 
					
						
							|  |  |  |       const typeConversion: {[key: string]: TClickhouseColumnDataType} = { | 
					
						
							|  |  |  |         string: 'String', | 
					
						
							|  |  |  |         number: 'Float64', | 
					
						
							|  |  |  |         undefined: null, | 
					
						
							|  |  |  |         null: null | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |       if (valueArg instanceof Array) { | 
					
						
							|  |  |  |         const arrayType = typeConversion[(typeof valueArg[0]) as string]; | 
					
						
							|  |  |  |         if (!arrayType) { | 
					
						
							|  |  |  |           return null; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           return `Array(${arrayType})` as TClickhouseColumnDataType; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |       return typeConversion[(typeof valueArg) as string]; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-03-08 15:12:51 +01:00
										 |  |  |     const checkPath = async (pathArg: string, typeArg: TClickhouseColumnDataType, prechecked = false) => { | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |       let columnFound = false; | 
					
						
							|  |  |  |       for (const column of this.columns) { | 
					
						
							|  |  |  |         if (pathArg === column.name) { | 
					
						
							|  |  |  |           columnFound = true; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (!columnFound) { | 
					
						
							| 
									
										
										
										
											2022-03-08 15:12:51 +01:00
										 |  |  |         if (!prechecked) { | 
					
						
							|  |  |  |           await this.updateColumns(); | 
					
						
							|  |  |  |           await checkPath(pathArg, typeArg, true); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |         const alterString = `ALTER TABLE ${this.options.tableName} ADD COLUMN ${pathArg} ${typeArg} FIRST` | 
					
						
							| 
									
										
										
										
											2022-03-08 15:12:51 +01:00
										 |  |  |         try { | 
					
						
							|  |  |  |           await this.smartClickHouseDbRef.clickhouseClient.queryPromise(`
 | 
					
						
							|  |  |  |           ${alterString} | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |         `);
 | 
					
						
							| 
									
										
										
										
											2022-03-08 15:12:51 +01:00
										 |  |  |         } catch(err) { | 
					
						
							|  |  |  |           console.log(alterString); | 
					
						
							|  |  |  |           for (const column of this.columns) { | 
					
						
							|  |  |  |             console.log(column.name); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |         await this.updateColumns(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // key checking
 | 
					
						
							|  |  |  |     const flatDataArg = plugins.smartobject.toFlatObject(dataArg); | 
					
						
							|  |  |  |     for (const key of Object.keys(flatDataArg)) { | 
					
						
							|  |  |  |       const value = flatDataArg[key]; | 
					
						
							|  |  |  |       if (key === 'timestamp' && typeof value !== 'number') { | 
					
						
							|  |  |  |         throw new Error('timestamp must be of type number'); | 
					
						
							|  |  |  |       } else if (key === 'timestamp') { | 
					
						
							|  |  |  |         storageJson.timestamp = flatDataArg[key]; | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       // lets deal with the rest
 | 
					
						
							|  |  |  |       const clickhouseType = getClickhouseTypeForValue(value); | 
					
						
							| 
									
										
										
										
											2022-03-08 15:12:51 +01:00
										 |  |  |       if (!clickhouseType) { | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |       await checkPath(key, clickhouseType); | 
					
						
							|  |  |  |       storageJson[key] = value; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-14 20:00:03 +01:00
										 |  |  |     const result = await this.smartClickHouseDbRef.clickhouseClient.insertPromise(this.options.tableName, [ | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |       storageJson, | 
					
						
							| 
									
										
										
										
											2022-07-27 22:42:08 +02:00
										 |  |  |     ]).catch(async () => { | 
					
						
							|  |  |  |       if (this.healingDeferred) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       this.healingDeferred = plugins.smartpromise.defer(); | 
					
						
							|  |  |  |       console.log(`Ran into an error. Trying to set up things properly again.`); | 
					
						
							|  |  |  |       await this.smartClickHouseDbRef.pingDatabaseUntilAvailable(); | 
					
						
							|  |  |  |       await this.smartClickHouseDbRef.createDatabase(); | 
					
						
							|  |  |  |       this.columns = []; | 
					
						
							|  |  |  |       this.healingDeferred.resolve(); | 
					
						
							|  |  |  |       this.healingDeferred = null; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-03-07 15:49:47 +01:00
										 |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |