diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml index 6f42550a..823d1ad4 100644 --- a/.github/workflows/test-and-release.yml +++ b/.github/workflows/test-and-release.yml @@ -30,10 +30,10 @@ jobs: steps: - uses: actions/checkout@v5 - - name: Use Node.js 18.x + - name: Use Node.js 22.x uses: actions/setup-node@v5 with: - node-version: 18.x + node-version: 22.x - name: Install Dependencies @@ -57,7 +57,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [18.x, 20.x, 22.x, 24.x] steps: - uses: actions/checkout@v5 @@ -73,6 +73,9 @@ jobs: - name: Install Dependencies run: npm install + - name: Build + run: npm run build + - name: Run MySQL tests run: node node_modules/mocha/bin/mocha test/testMySQL*.js --exit env: @@ -94,7 +97,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [18.x, 20.x, 22.x, 24.x] # Service containers to run with `container-job` services: @@ -125,6 +128,9 @@ jobs: - name: Install Dependencies run: npm install + - name: Build + run: npm run build + - name: Run Postgres tests run: node node_modules/mocha/bin/mocha test/testPostgreSQL.js --exit env: @@ -146,7 +152,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [18.x, 20.x, 22.x, 24.x] # Service containers to run with `container-job` services: @@ -169,6 +175,9 @@ jobs: - name: Install Dependencies run: npm install + - name: Build + run: npm run build + - name: Run MSSQL tests run: node node_modules/mocha/bin/mocha test/testMSSQL.js --exit env: @@ -190,7 +199,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [18.x, 20.x, 22.x, 24.x] os: [ubuntu-latest, windows-latest, macos-latest] steps: @@ -203,6 +212,9 @@ jobs: - name: Install Dependencies run: npm install + - name: Build + run: npm run build + - name: Run SQLite tests run: node node_modules/mocha/bin/mocha test/testSQLite.js --exit - name: Run Commons @@ -229,7 +241,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [14.x] + node-version: [22.x] steps: - name: Checkout code diff --git a/.releaseconfig.json b/.releaseconfig.json index bb9bf402..65cb56b1 100644 --- a/.releaseconfig.json +++ b/.releaseconfig.json @@ -1,3 +1,6 @@ { - "plugins": ["iobroker", "license"] + "plugins": ["iobroker", "license"], + "exec": { + "before_commit": "npm run build" + } } diff --git a/LICENSE b/LICENSE index 15a33213..81f63c01 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2024 bluefox , Apollon77 +Copyright (c) 2015-2025 bluefox , Apollon77 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 329c52dd..ddb76872 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This adapter saves state history into SQL DB. Supports PostgreSQL, mysql, Microsoft SQL Server and sqlite. You can leave port 0 if the default port is desired. -**This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** For more details and for information how to disable the error reporting see [Sentry-Plugin Documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry)! Sentry reporting is used starting with js-controller 3.0. +**This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** For more details and for information how to disable the error reporting, see [Sentry-Plugin Documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry)! Sentry reporting is used starting with js-controller 3.0. ## Settings @@ -30,15 +30,15 @@ You can leave port 0 if the default port is desired. - **Do not create database**: Activate this option if a database already created (e.g. by administrator) and the ioBroker-user does not have enough rights to create a DB. ## Default Settings -- **Debounce Time** - Protection against unstable values to make sure that only stable values are logged when the value did not change in the defined amount of Milliseconds. ATTENTION: If values change more often then this setting effectively no value will be logged (because any value is unstable) -- **Blocktime** - Defines for how long after storing the last value no further value is stored. When the given time in Milliseconds is over then the next value that fulfills all other checks is logged. +- **Debounce Time** - Protection against unstable values to make sure that only stable values are logged when the value did not change in the defined amount of Milliseconds. ATTENTION: If values change more often than this setting effectively, no value will be logged (because any value is unstable) +- **Blocktime** - Defines for how long after storing the last value no further value is stored. When the given time in Milliseconds is over, then the next value that fulfills all other checks is logged. - **Record changes only** - This function makes sure that only changed values are logged if they fulfill other checks (see below). Same values will not be logged. - **still record the same values (seconds)** - When using "Record changes only" you can set a time interval in seconds here after which also unchanged values will be re-logged into the DB. You can detect the values re-logged by the adapter with the "from" field. -- **Minimum difference from last value** - When using "Record changes only" you can define the required minimum difference between the new value and the last value. If this is not reached the value is not recorded. +- **Minimum difference from the last value** - When using "Record changes only" you can define the required minimum difference between the new value and the last value. If this is not reached, the value is not recorded. - **ignore 0 or null values (==0)** - You can define if 0 or null values should be ignored. - **ignore values below zero (<0)** - You can define if values below zero should be ignored. - **Disable charting optimized logging of skipped values** - By default, the adapter tries to record the values for optimized charting. This can mean that additional values (that e.g. not fulfilled all checks above) are logged automatically. If this is not wanted, you can disable this feature. -- **Alias-ID** - You can define an alias for the ID. This is useful if you have changed a device and want to have continuous data logging. Please consider switching to real alias States in teh future! +- **Alias-ID** - You can define an alias for the ID. This is useful if you have changed a device and want to have continuous data logging. Please consider switching to real alias States in the future! - **Storage retention** - How many values in the past will be stored on disk. Data are deleted when the time is reached as soon as new data should be stored for a datapoint. - **Maximal number of stored in RAM values** - Define how many numbers of values will be held in RAM before persisting them on disk. You can control how much I/O is done. - **Enable enhanced debug logs for the datapoint** - If you want to see more detailed logs for this datapoint, you can enable this option. You still need to enable "debug" loglevel for these additional values to be visible! This helps in debugging issues or understanding why the adapter is logging a value (or not). @@ -48,7 +48,7 @@ Most of these values can be pre-defined in the instance settings and are then pr ## Database installation tips ### MS-SQL: -Use ```localhost\instance``` for the host and check TCP/IP connections enabled. +Use `localhost\instance` for the host and check TCP/IP connections enabled. https://msdn.microsoft.com/en-us/library/bb909712(v=vs.90).aspx ### SQLite: @@ -56,19 +56,13 @@ is "file"-DB and cannot manage too many events. If you have a big amount of data SQLite DB must not be installed extra. It is just a file on disk, but to install it you require build tools on your system. For linux, just write: -``` +```bash sudo apt-get install build-essential ``` -For windows: - -``` -c:\>npm install --global --production windows-build-tools -``` - -and then reinstall the adapter, e.g: +For windows install node.js with "Automatically install the necessary tools..."-option and then reinstall the adapter, e.g: -``` +```bash cd /opt/iobroker iobroker stop sql npm install iobroker.sql --production @@ -78,7 +72,7 @@ iobroker start sql ### MySQL: You can install mysql on linux systems as following: -``` +```bash apt-get install mysql-server mysql-client mysql -u root -p @@ -90,7 +84,7 @@ FLUSH PRIVILEGES; If required, edit */etc/mysql/my.cnf* to set bind to IP-Address for remote connecting. -**Warning**: iobroker user is "admin". If required give limited rights to iobroker user. +**Warning**: iobroker user is "admin". If required, give limited rights to iobroker user. On the "windows" it can be easily installed via installer: https://dev.mysql.com/downloads/installer/. @@ -101,7 +95,7 @@ Pay attention to the authentication method. The new encryption algorithm in MySQ ## Structure of the DBs The default Database name is `iobroker`, but it can be changed in the configuration. ### Sources -This table is a list of adapter's instances, that wrote the entries. (state.from) +This table is a list of adapter's instances that wrote the entries. (state.from) | DB | Name in query | |------------|----------------------| @@ -142,8 +136,8 @@ Structure: ### Numbers Values for states with type "number". **ts** means "time series". -| DB | Name in query | -|------------|-------------------------| +| DB | Name in query | +|------------|------------------------| | MS-SQL | iobroker.dbo.ts_number | | MySQL | iobroker.ts_number | | PostgreSQL | ts_number | @@ -151,18 +145,19 @@ Values for states with type "number". **ts** means "time series". Structure: -| Field | Type | Description | -|--------|--------------------------------------------|-------------------------------------------------| -| id | INTEGER | ID of state from "Data points" table | -| ts | BIGINT / INTEGER | Time in ms till epoch. Can be converted to time with "new Date(ts)" | -| val | REAL | Value | -| ack | BIT/BOOLEAN | Is acknowledged: 0 - not ack, 1 - ack | -| _from | INTEGER | ID of source from "Sources" table | +| Field | Type | Description | +|--------|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| +| id | INTEGER | ID of state from "Data points" table | +| ts | BIGINT / INTEGER | Time in ms till epoch. Can be converted to time with "new Date(ts)" | +| val | REAL | Value | +| ack | BIT/BOOLEAN | Is acknowledged: 0 - not ack, 1 - ack | +| _from | INTEGER | ID of source from "Sources" table | | q | INTEGER | Quality as number. You can find description [here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#states) | *Note:* MS-SQL uses BIT, and others use BOOLEAN. SQLite uses for ts INTEGER and all others BIGINT. The user can define additional to type `number` the functionality of `counters`. For this purpose, the following table is created: + | DB | Name in the query | |------------|-------------------------| | MS-SQL | iobroker.dbo.ts_counter | @@ -172,19 +167,19 @@ The user can define additional to type `number` the functionality of `counters`. Structure: -| Field | Type | Description | -|--------|--------------------------------------------|-------------------------------------------------| -| id | INTEGER | ID of state from "Data points" table | -| ts | BIGINT / INTEGER | Time in ms till epoch. Can be converted to time with "new Date(ts)" | -| val | REAL | Value | +| Field | Type | Description | +|--------|------------------|---------------------------------------------------------------------| +| id | INTEGER | ID of state from "Data points" table | +| ts | BIGINT / INTEGER | Time in ms till epoch. Can be converted to time with "new Date(ts)" | +| val | REAL | Value | This table stores the values when the counter was exchanged and the value does not increase, but failed to zero or lower value. ### Strings Values for states with type `string`. -| DB | Name in query | -|------------|-------------------------| +| DB | Name in query | +|------------|------------------------| | MS-SQL | iobroker.dbo.ts_string | | MySQL | iobroker.ts_string | | PostgreSQL | ts_string | @@ -192,14 +187,14 @@ Values for states with type `string`. Structure: -| Field | Type | Description | -|--------|--------------------------------------------|-------------------------------------------------| -| id | INTEGER | ID of state from "Data points" table | -| ts | BIGINT | Time in ms till epoch. Can be converted to time with "new Date(ts)" | -| val | TEXT | Value | -| ack | BIT/BOOLEAN | Is acknowledged: 0 - not ack, 1 - ack | -| _from | INTEGER | ID of source from "Sources" table | -| q | INTEGER | Quality as number. You can find description [here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#states) | +| Field | Type | Description | +|--------|-----------------------|---------------------------------------------------------------------------------------------------------------------------| +| id | INTEGER | ID of state from "Data points" table | +| ts | BIGINT | Time in ms till epoch. Can be converted to time with "new Date(ts)" | +| val | TEXT | Value | +| ack | BIT/BOOLEAN | Is acknowledged: 0 - not ack, 1 - ack | +| _from | INTEGER | ID of source from "Sources" table | +| q | INTEGER | Quality as number. You can find description [here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#states) | *Note:* MS-SQL uses BIT, and others use BOOLEAN. SQLite uses for ts INTEGER and all others BIGINT. @@ -215,22 +210,22 @@ Values for states with type `boolean`. Structure: -| Field | Type | Description | -|--------|--------------------------------------------|-------------------------------------------------| -| id | INTEGER | ID of state from "Data points" table | -| ts | BIGINT | Time in ms till epoch. Can be converted to time with "new Date(ts)" | -| val | BIT/BOOLEAN | Value | -| ack | BIT/BOOLEAN | Is acknowledged: 0 - not ack, 1 - ack | -| _from | INTEGER | ID of source from "Sources" table | -| q | INTEGER | Quality as number. You can find description [here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#states) | +| Field | Type | Description | +|--------|-------------|---------------------------------------------------------------------------------------------------------------------------| +| id | INTEGER | ID of state from "Data points" table | +| ts | BIGINT | Time in ms till epoch. Can be converted to time with "new Date(ts)" | +| val | BIT/BOOLEAN | Value | +| ack | BIT/BOOLEAN | Is acknowledged: 0 - not ack, 1 - ack | +| _from | INTEGER | ID of source from "Sources" table | +| q | INTEGER | Quality as number. You can find description [here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#states) | *Note:* MS-SQL uses BIT, and others use BOOLEAN. SQLite uses for ts INTEGER and all others BIGINT. ## Access values from Javascript adapter -The sorted values can be accessed from Javascript adapter. +The sorted values can be accessed from JavaScript adapter. * Get 50 last stored events for all IDs -``` +```js sendTo('sql.0', 'getHistory', { id: '*', options: { @@ -247,7 +242,7 @@ sendTo('sql.0', 'getHistory', { ``` * Get stored values for "system.adapter.admin.0.memRss" in last hour -``` +```js var end = Date.now(); sendTo('sql.0', 'getHistory', { id: 'system.adapter.admin.0.memRss', @@ -265,8 +260,8 @@ sendTo('sql.0', 'getHistory', { ``` Possible options: -- **start** - (optional) time in ms - *Date.now()*' -- **end** - (optional) time in ms - *Date.now()*', by default is (now + 5000 seconds) +- **start** - (optional) time in ms - *Date.now()* +- **end** - (optional) time in ms - *Date.now()*, by default is `(now + 5000 seconds)` - **step** - (optional) used in aggregate (max, min, average, total, ...) step in ms of intervals - **count** - number of values if aggregate is 'onchange' or number of intervals if other aggregate method. Count will be ignored if a step is set, else default is 500 if not set - **from** - if *from* field should be included in answer @@ -277,8 +272,8 @@ Possible options: - **round** - round result to number of digits after decimal point - **ignoreNull** - if null values should be included (false), replaced by last not null value (true) or replaced with 0 (0) - **removeBorderValues** - By default, additional border values are returned to optimize charting. Set this option to true if this is not wanted (e.g. for script data processing) -- **returnNewestEntries** - The returned data are always sorted by timestamp ascending. When using aggregate "none" and also providing "count" or "limit" this means that normally the oldest entries are returned (unless no start data is provided). Set this option to true to get the newest entries instead. -- **aggregate** - aggregate method (Default: 'average'): +- **returnNewestEntries** - The returned data are always sorted by timestamp ascending. When using aggregate "none" and also providing "count" or "limit", this means that normally the oldest entries are returned (unless no start data is provided). Set this option to true to get the newest entries instead. +- **aggregate** - aggregate method (Default: `average`): - *minmax* - used special algorithm. Splice the whole time range in small intervals and find for every interval max, min, start and end values. - *max* - Splice the whole time range in small intervals and find for every interval max value and use it for this interval (nulls will be ignored). - *min* - Same as max, but take minimal value. @@ -286,7 +281,7 @@ Possible options: - *total* - Same as max, but calculate total value. - *count* - Same as max, but calculate number of values (nulls will be calculated). - *percentile* - Calculate n-th percentile (n is given in `options.percentile` or defaults to 50 if not provided). - - *quantile* - Calculate n quantile (n is given in options.quantile or defaults to 0.5 if not provided). + - *quantile* - Calculate n quantile (n is given in `options.quantile` or defaults to 0.5 if not provided). - *integral* - Calculate integral (additional parameters see below). - *none* - No aggregation at all. Only raw values in a given period. - **percentile** - (optional) when using aggregate method "percentile" defines the percentile level (0..100)(defaults to 50) @@ -297,13 +292,13 @@ Possible options: - *none* - no/stepwise interpolation The first and last points will be calculated for aggregations, except aggregation `none`. -If you manually request some aggregation, you should ignore first and last values, because they are calculated from values outside of a period. +If you manually request some aggregation, you should ignore first and last values because they are calculated from values outside of a period. ## Get counter User can ask the value of some counter (type=number, counter=true) for a specific period. -``` +```js var now = Date.now(); // get consumption value for last 30 days sendTo('sql.0', 'getCounter', { @@ -321,7 +316,7 @@ If the counter-device is replaced, it will be calculated too. ## Custom queries The user can execute custom queries on tables from javascript adapter: -``` +```js sendTo('sql.0', 'query', 'SELECT * FROM datapoints', function (result) { if (result.error) { console.error(result.error); @@ -333,7 +328,7 @@ sendTo('sql.0', 'query', 'SELECT * FROM datapoints', function (result) { ``` Or get entries for the last hour for ID=system.adapter.admin.0.memRss -``` +```js sendTo('sql.0', 'query', 'SELECT id FROM datapoints WHERE name="system.adapter.admin.0.memRss"', function (result) { if (result.error) { console.error(result.error); @@ -355,21 +350,20 @@ Depending on the database, the database name or database name + schema must be i Example if your database is called 'iobroker': -| DB | Name in query | -|------------|------------------------------------------| -| MS-SQL | SELECT * FROM iobroker.dbo.datapoints ...| -| MySQL | SELECT * FROM iobroker.datapoints ... | +| DB | Name in query | +|---------|---------------------------------------------| +| MS-SQL | `SELECT * FROM iobroker.dbo.datapoints ...` | +| MySQL | `SELECT * FROM iobroker.datapoints ...` | ## storeState -If you want to write other data into the SQL database you can use the build +If you want to write other data into the SQL database, you can use the build in system function **storeState**. This function can also be used to convert data from other History adapters like InfluxDB or SQL. -A successful response do not mean that the data are really written out to -the disk. It just means that they were processed! +A successful response does not mean that the data is really written out to the disk. It just means that they were processed! -The given ids are not checked against the ioBroker database and do not need to be set up or enabled there. If own IDs are used without settings then the "rules" parameter is not supported and will result in an error. The default "Maximal number of stored in RAM values" is used for such IDs. +The given ids are not checked against the ioBroker database and do not need to be set up or enabled there. If own IDs are used without settings, then the "rules" parameter is not supported and will result in an error. The default "Maximal number of stored in RAM values" is used for such IDs. The Message can have one of the following three formats: @@ -438,42 +432,42 @@ In case of errors, an array with all single error messages is returned and also ## delete state If you want to delete entry from the Database, you can use the build in system function **delete**: -``` +```javascript sendTo('sql.0', 'delete', [ - {id: 'mbus.0.counter.xxx', state: {ts: 1589458809352}, - {id: 'mbus.0.counter.yyy', state: {ts: 1589458809353} + {id: 'mbus.0.counter.xxx', state: {ts: 1589458809352}}, + {id: 'mbus.0.counter.yyy', state: {ts: 1589458809353}}, ], result => console.log('deleted')); ``` To delete ALL history data for some data point, execute: -``` +```javascript sendTo('sql.0', 'deleteAll', [ - {id: 'mbus.0.counter.xxx'} + {id: 'mbus.0.counter.xxx'}, {id: 'mbus.0.counter.yyy'} ], result => console.log('deleted')); ``` To delete history data for some data point and for some range, execute: -``` +```javascript sendTo('sql.0', 'deleteRange', [ {id: 'mbus.0.counter.xxx', start: '2019-01-01T00:00:00.000Z', end: '2019-12-31T23:59:59.999'}, {id: 'mbus.0.counter.yyy', start: 1589458809352, end: 1589458809353} ], result => console.log('deleted')); ``` -Time could be ms since epoch or ans string, that could be converted by javascript Date object. +Time could be ms since epoch or ans string, that could be converted by JavaScript Date object. Values will be deleted including defined limits. `ts >= start AND ts <= end` ## change state If you want to change entry's value, quality or acknowledge flag in the database, you can use the build in system function **update**: -``` +```javascript sendTo('sql.0', 'update', [ - {id: 'mbus.0.counter.xxx', state: {ts: 1589458809352, val: 15, ack: true, q: 0}, - {id: 'mbus.0.counter.yyy', state: {ts: 1589458809353, val: 16, ack: true, q: 0} + {id: 'mbus.0.counter.xxx', state: {ts: 1589458809352, val: 15, ack: true, q: 0}}, + {id: 'mbus.0.counter.yyy', state: {ts: 1589458809353, val: 16, ack: true, q: 0}}, ], result => console.log('deleted')); ``` @@ -487,7 +481,7 @@ The adapter supports enabling and disabling of history logging via JavaScript an ### enable The message requires having the "id" of the data point. Additionally, optional "options" to define the data point specific settings: -``` +```javascript sendTo('sql.0', 'enableHistory', { id: 'system.adapter.sql.0.memRss', options: { @@ -511,7 +505,7 @@ sendTo('sql.0', 'enableHistory', { ### disable The message requires having the "id" of the data point. -``` +```javascript sendTo('sql.0', 'disableHistory', { id: 'system.adapter.sql.0.memRss', }, function (result) { @@ -527,10 +521,10 @@ sendTo('sql.0', 'disableHistory', { ### get List The message has no parameters. -``` +```javascript sendTo('sql.0', 'getEnabledDPs', {}, function (result) { //result is object like: - { + console.log({ "system.adapter.sql.0.memRss": { "changesOnly":true, "debounce":0, @@ -540,9 +534,9 @@ sendTo('sql.0', 'getEnabledDPs', {}, function (result) { "enabled":true, "changesRelogInterval":0, "aliasId": "" - } - ... - } + }, + // ... + }); }); ``` @@ -612,17 +606,17 @@ sendTo('sql.0', 'getEnabledDPs', {}, function (result) { * (Apollon77) Add flag returnNewestEntries for GetHistory to determine which records to return when more entries as "count" are existing for aggregate "none" * (Apollon77) Add support for addId getHistory flag for GetHistory * (Apollon77) Add new Debug flag to enable/disable debug logging on datapoint level (default is false) to optimize performance -* (Apollon77) Add aggregate method "percentile" to calculate the percentile (0..100) of the values (requires options.percentile with the percentile level, defaults to 50 if not provided). Basically same as Quantile just different levels are used -* (Apollon77) Add aggregate method "quantile" to calculate the quantile (0..1) of the values (requires options.quantile with the quantile level, defaults to 0.5 if not provided). Basically same as Percentile just different levels are used -* (Apollon77) Add (experimental) method "integral" to calculate the integral of the values. Requires options.integralUnit with the time duration of the integral in seconds, defaults to 60s if not provided. Optionally a linear interpolation can be done by setting options.integralInterpolation to "linear" -* (Apollon77) When request contains flag removeBorderValues: true, the result then cut the additional pre and post border values out of the results +* (Apollon77) Add aggregate method "percentile" to calculate the percentile (0..100) of the values (requires `options.percentile` with the percentile level, defaults to 50 if not provided). Basically the same as Quantile, just different levels are used +* (Apollon77) Add aggregate method "quantile" to calculate the quantile (0..1) of the values (requires `options.quantile` with the quantile level, defaults to 0.5 if not provided). Basically the same as Percentile just different levels are used +* (Apollon77) Add (experimental) method "integral" to calculate the integral of the values. Requires options.integralUnit with the time duration of the integral in seconds, defaults to 60s if not provided. Optionally, a linear interpolation can be done by setting options.integralInterpolation to "linear" +* (Apollon77) When request contains flag removeBorderValues: true, the result then cut the additional pre- and post-border values out of the results * (Apollon77) Enhance the former "Ignore below 0" feature and now allow specifying to ignore below or above specified values. The old setting is converted to the new one * (Apollon77) Upgrade MSSQL and MySQL drivers incl. Support for MySQL 8 * (Apollon77) Make sure that min change delta allows numbers entered with comma (german notation) in all cases * (Apollon77) Add support to specify how to round numbers on query per datapoint * (Apollon77) Do not log passwords for Postgres connections -* (Apollon77) Optimize SSL support for database connections including option to allow self signed certificates -* (Apollon77) Allow to specify custom retention duration in days +* (Apollon77) Optimize SSL support for database connections including option to allow self-signed certificates +* (Apollon77) Allows to specify custom retention duration in days * (winnyschuster) Fix Insert statement for MSSQL ts_counter * (winnyschuster) type of ts in user queries corrected @@ -891,7 +885,7 @@ sendTo('sql.0', 'getEnabledDPs', {}, function (result) { The MIT License (MIT) -Copyright (c) 2015-2024 bluefox , Apollon77 +Copyright (c) 2015-2025 bluefox , Apollon77 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/admin/jsonConfig.json b/admin/jsonConfig.json index 05fab462..73a62b6b 100644 --- a/admin/jsonConfig.json +++ b/admin/jsonConfig.json @@ -1,335 +1,437 @@ { - "type": "tabs", - "i18n": true, - "items": { - "dbTab": { - "type": "panel", - "label": "DB settings", - "items": { - "dbtype": { - "type": "select", - "noTranslation": true, - "options": [ - {"label": "MySQL", "value": "mysql"}, - {"label": "PostgreSQL", "value": "postgresql"}, - {"label": "SQLite3", "value": "sqlite"}, - {"label": "MS-SQL", "value": "mssql"} - ], - "label": "DB Type", - "sm": 12, - "md": 4, - "lg": 3 - }, + "type": "tabs", + "i18n": true, + "items": { + "dbTab": { + "type": "panel", + "label": "DB settings", + "items": { + "dockerMysql.enabled": { + "newLine": true, + "type": "checkDocker", + "label": "Use self-hosted MySQL in Docker", + "help": "docker_explanation", + "xs": 12 + }, + "dockerPhpMyAdmin.enabled": { + "newLine": true, + "type": "checkDocker", + "hideVersion": true, + "hidden": "!data.dockerMysql?.enabled", + "label": "Use self-hosted PhpMyAdmin in Docker", + "xs": 12 + }, + "dbtype": { + "type": "select", + "noTranslation": true, + "hidden": "!!data.dockerMysql?.enabled", + "options": [ + { "label": "MySQL", "value": "mysql" }, + { "label": "PostgreSQL", "value": "postgresql" }, + { "label": "SQLite3", "value": "sqlite" }, + { "label": "MS-SQL", "value": "mssql" } + ], + "label": "DB Type", + "sm": 12, + "md": 4, + "lg": 3 + }, - "host": { - "newLine": true, - "type": "text", - "label": "Host", - "hidden": "data.dbtype === 'sqlite'", - "sm": 12, - "md": 4, - "lg": 3 - }, - "port": { - "type": "number", - "label": "Port", - "hidden": "data.dbtype === 'sqlite'", - "min": 0, - "max": 65565, - "sm": 12, - "md": 4, - "lg": 3 - }, - "dbname": { - "type": "text", - "label": "DB Name", - "hidden": "data.dbtype === 'sqlite'", - "sm": 12, - "md": 4, - "lg": 3 - }, - - "user": { - "newLine": true, - "type": "text", - "label": "User", - "hidden": "data.dbtype === 'sqlite'", - "sm": 12, - "md": 4, - "lg": 3 - }, - "password": { - "type": "password", - "label": "Password", - "repeat": true, - "hidden": "data.dbtype === 'sqlite'", - "sm": 12, - "md": 8, - "lg": 6 - }, + "host": { + "newLine": true, + "type": "text", + "label": "Host", + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "sm": 12, + "md": 4, + "lg": 3 + }, + "port": { + "type": "number", + "label": "Port", + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "min": 0, + "max": 65565, + "sm": 12, + "md": 4, + "lg": 3 + }, + "dbname": { + "type": "text", + "label": "DB Name", + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "sm": 12, + "md": 4, + "lg": 3 + }, + "user": { + "newLine": true, + "type": "text", + "label": "User", + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "sm": 12, + "md": 4, + "lg": 3 + }, + "password": { + "type": "password", + "label": "Password", + "repeat": true, + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "sm": 12, + "md": 8, + "lg": 6 + }, - "fileName": { - "newLine": true, - "type": "text", - "label": "File for sqlite", - "hidden": "data.dbtype !== 'sqlite'", - "help": "Input path with the file name.", - "sm": 12, - "md": 4, - "lg": 3 - }, - "requestInterval": { - "type": "number", - "label": "requestInterval", - "hidden": "data.dbtype !== 'sqlite'", - "sm": 12, - "md": 4, - "lg": 3 - }, + "fileName": { + "newLine": true, + "type": "text", + "label": "File for sqlite", + "hidden": "data.dbtype !== 'sqlite' || !!data.dockerMysql?.enabled", + "help": "Input path with the file name.", + "sm": 12, + "md": 4, + "lg": 3 + }, + "requestInterval": { + "type": "number", + "label": "requestInterval", + "hidden": "data.dbtype !== 'sqlite'", + "sm": 12, + "md": 4, + "lg": 3 + }, - "encrypt": { - "newLine": true, - "type": "checkbox", - "hidden": "data.dbtype === 'sqlite'", - "label": "Encrypt", - "sm": 12, - "md": 4, - "lg": 3 - }, - "rejectUnauthorized": { - "type": "checkbox", - "hidden": "data.dbtype === 'sqlite'", - "label": "Reject on SSL errors", - "sm": 12, - "md": 4, - "lg": 3 - }, - "multiRequests": { - "newLine": true, - "type": "checkbox", - "hidden": "data.dbtype === 'sqlite'", - "label": "Allow parallel requests", - "sm": 12, - "md": 4, - "lg": 3 - }, - "maxConnections": { - "type": "number", - "hidden": "data.dbtype === 'sqlite'", - "label": "Maximum concurrent connections", - "sm": 12, - "md": 4, - "lg": 3 - }, - "doNotCreateDatabase": { - "type": "checkbox", - "hidden": "data.dbtype === 'sqlite'", - "label": "Do not create database (already created)", - "sm": 12, - "md": 4, - "lg": 6 - }, - "_testConnection": { - "newLine": true, - "variant": "contained", - "color": "primary", - "disabled": "!_alive", - "type": "sendTo", - "error": { - "connect timeout": "Connection timeout" - }, - "icon": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDMyIDMyIiB3aWR0aD0iNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICAgIDxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0ibTIyLjk4NSAxMy42OTJoLS4wMTl2LTEuNzQ2YS44MjIuODIyIDAgMCAwIDAtLjEzOSAxIDEgMCAwIDAgLS4wMTEtLjE0NXYtNS4zNjhhLjcwOC43MDggMCAwIDAgMC0uMTM5IDIuNjQgMi42NCAwIDAgMCAtMS42MzQtMi4xNzFjLTEuNzUtLjk1NC01LjI2OS0xLjY0NS05LjMzLTEuNjQ1cy03LjU3OS42OTEtOS4zMjkgMS42NDVhMi42NCAyLjY0IDAgMCAwIC0xLjYzNSAyLjE2Ny43ODguNzg4IDAgMCAwIDAgLjEzNWwtLjAxIDUuNTIyYTEuMDQxIDEuMDQxIDAgMCAwIC4wMi4yNTZsLS4wMSA1LjRhLjg4NS44ODUgMCAwIDAgLjAxLjE5MWwtLjAxIDUuNDU1cy0uMTE4IDEuMTkgMS43ODQgMi4yNTVjMS4zMjguNzQ0IDMuOTU2IDEuNTM1IDkuMTg5IDEuNTM1YTM3LjA5MSAzNy4wOTEgMCAwIDAgNC43MTUtLjI2NCA3Ljk5MyA3Ljk5MyAwIDEgMCA2LjI2OC0xMi45NDV6bS0xOS42NjEtNy43NzJhNC43MDYgNC43MDYgMCAwIDEgLjk4Ni0uNDk0IDIzLjExMiAyMy4xMTIgMCAwIDEgNy42ODEtMS4wODcgMjMuMTExIDIzLjExMSAwIDAgMSA3LjY4MSAxLjA4NyA0LjczOSA0LjczOSAwIDAgMSAuOTg3LjQ5NCAxLjcgMS43IDAgMCAxIC4yMzIuMjA5IDIuMjc2IDIuMjc2IDAgMCAxIC0uMzE4LjMxOCA0Ljk4NCA0Ljk4NCAwIDAgMSAtLjkuNDM4IDIzLjE0IDIzLjE0IDAgMCAxIC03LjY4MSAxLjA4NiAyMy4xNDEgMjMuMTQxIDAgMCAxIC03LjY4Mi0xLjA4NiA1LjAxOSA1LjAxOSAwIDAgMSAtLjktLjQzOCAyLjMzNCAyLjMzNCAwIDAgMSAtLjMxOC0uMzE4IDEuNzcgMS43NyAwIDAgMSAuMjMyLS4yMDl6bS0uMyAyLjY0M2MxLjM4Ny43IDQuMDA1IDEuNDA4IDguOTY4IDEuNDA4czcuNTc1LS43IDguOTY0LTEuNDA2djMuMTEyYTIuMzE2IDIuMzE2IDAgMCAxIC0uNzM3LjY0N2MtMS4xNzQuNjY0LTMuNTMgMS4yNzktOC4yMjcgMS4yNzktNC42NjkgMC03LjAyOC0uNjE2LTguMjEyLTEuMjhhMi40NTUgMi40NTUgMCAwIDEgLS43NS0uNjQ2cTAtLjAzNi0uMDEyLS4wNzJ6bTguOTc2IDE2LjMzN2MtNC42NyAwLTcuMDI5LS42MTctOC4yMTMtMS4yODFhMi40MzggMi40MzggMCAwIDEgLS43NjItLjY2MmwuMDA2LTMuMWMxLjM4OC43IDQuMDA2IDEuNDA4IDguOTY5IDEuNDA4IDEuMTE3IDAgMi4xMTItLjAzNSAzLS4xLS4wMTEuMTY3LS4wMTYuMzM1LS4wMTYuNTA1YTcuOTM4IDcuOTM4IDAgMCAwIC42MTUgMy4wNzJjLTEuMDE4LjEwMy0yLjIwOC4xNTgtMy41OTkuMTU4em0zLjQtNS43OGMtLjk3NS4wODUtMi4xLjEzNC0zLjQuMTM0LTQuNjcgMC03LjAyOS0uNjE2LTguMjEzLTEuMjhhMi40MjkgMi40MjkgMCAwIDEgLS43NjEtLjY2MmwuMDA2LTMuMWMxLjM4OC43IDQuMDA2IDEuNDA4IDguOTY5IDEuNDA4YTMwLjIyMyAzMC4yMjMgMCAwIDAgNi41MzktLjU4NSA4LjAyIDguMDIgMCAwIDAgLTMuMTQgNC4wODZ6bTcuNTgxIDguNTRhNS45ODggNS45ODggMCAwIDEgLTUuODUxLTcuMzE1Yy4wMDYtLjAxOS4wMTEtLjAzOS4wMTYtLjA1OHMuMDA5LS4wMzkuMDEyLS4wNTlhNiA2IDAgMSAxIDUuODIzIDcuNDMyeiIvPg0KICAgIDxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0ibTE5LjYzOSAyMS45YTEgMSAwIDAgMCAxLjQxOCAxLjQxMSAyLjYyOCAyLjYyOCAwIDAgMSAzLjcyNSAwIDEgMSAwIDAgMCAxLjQxOC0xLjQxMSA0LjYyOCA0LjYyOCAwIDAgMCAtNi41NjEgMHoiLz4NCiAgICA8ZWxsaXBzZSBmaWxsPSJjdXJyZW50Q29sb3IiIGN4PSIyMi45ODUiIGN5PSIyNC44NDMiIHJ4PSIxLjY2NyIgcnk9IjEuNjc0Ii8+DQogICAgPHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJtMjcuOTc5IDE5Ljc2OGE3LjA0NSA3LjA0NSAwIDAgMCAtOS45ODggMCAxIDEgMCAxIDAgMS40MTcgMS40MTIgNS4wNDUgNS4wNDUgMCAwIDEgNy4xNTQgMCAxIDEgMCAxIDAgMS40MTctMS40MTJ6Ii8+DQo8L3N2Zz4=", - "command": "test", - "jsonData": "{\"config\": {\"dbtype\": \"${data.dbtype}\", \"port\": \"${data.port}\", \"host\": \"${data.host}\", \"user\": \"${data.user}\", \"fileName\": \"${data.fileName}\", \"password\": \"${data.password}\", \"encrypt\": ${data.encrypt}, \"rejectUnauthorized\": ${data.rejectUnauthorized}}}", - "label": "Test connection" - }, + "encrypt": { + "newLine": true, + "type": "checkbox", + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "label": "Encrypt", + "sm": 12, + "md": 4, + "lg": 3 + }, + "rejectUnauthorized": { + "type": "checkbox", + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "label": "Reject on SSL errors", + "sm": 12, + "md": 4, + "lg": 3 + }, + "multiRequests": { + "newLine": true, + "type": "checkbox", + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "label": "Allow parallel requests", + "sm": 12, + "md": 4, + "lg": 3 + }, + "maxConnections": { + "type": "number", + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "label": "Maximum concurrent connections", + "sm": 12, + "md": 4, + "lg": 3 + }, + "doNotCreateDatabase": { + "type": "checkbox", + "hidden": "data.dbtype === 'sqlite' || !!data.dockerMysql?.enabled", + "label": "Do not create database (already created)", + "sm": 12, + "md": 4, + "lg": 6 + }, + "_testConnection": { + "newLine": true, + "variant": "contained", + "color": "primary", + "disabled": "!_alive", + "type": "sendTo", + "error": { + "connect timeout": "Connection timeout" + }, + "icon": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDMyIDMyIiB3aWR0aD0iNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICAgIDxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0ibTIyLjk4NSAxMy42OTJoLS4wMTl2LTEuNzQ2YS44MjIuODIyIDAgMCAwIDAtLjEzOSAxIDEgMCAwIDAgLS4wMTEtLjE0NXYtNS4zNjhhLjcwOC43MDggMCAwIDAgMC0uMTM5IDIuNjQgMi42NCAwIDAgMCAtMS42MzQtMi4xNzFjLTEuNzUtLjk1NC01LjI2OS0xLjY0NS05LjMzLTEuNjQ1cy03LjU3OS42OTEtOS4zMjkgMS42NDVhMi42NCAyLjY0IDAgMCAwIC0xLjYzNSAyLjE2Ny43ODguNzg4IDAgMCAwIDAgLjEzNWwtLjAxIDUuNTIyYTEuMDQxIDEuMDQxIDAgMCAwIC4wMi4yNTZsLS4wMSA1LjRhLjg4NS44ODUgMCAwIDAgLjAxLjE5MWwtLjAxIDUuNDU1cy0uMTE4IDEuMTkgMS43ODQgMi4yNTVjMS4zMjguNzQ0IDMuOTU2IDEuNTM1IDkuMTg5IDEuNTM1YTM3LjA5MSAzNy4wOTEgMCAwIDAgNC43MTUtLjI2NCA3Ljk5MyA3Ljk5MyAwIDEgMCA2LjI2OC0xMi45NDV6bS0xOS42NjEtNy43NzJhNC43MDYgNC43MDYgMCAwIDEgLjk4Ni0uNDk0IDIzLjExMiAyMy4xMTIgMCAwIDEgNy42ODEtMS4wODcgMjMuMTExIDIzLjExMSAwIDAgMSA3LjY4MSAxLjA4NyA0LjczOSA0LjczOSAwIDAgMSAuOTg3LjQ5NCAxLjcgMS43IDAgMCAxIC4yMzIuMjA5IDIuMjc2IDIuMjc2IDAgMCAxIC0uMzE4LjMxOCA0Ljk4NCA0Ljk4NCAwIDAgMSAtLjkuNDM4IDIzLjE0IDIzLjE0IDAgMCAxIC03LjY4MSAxLjA4NiAyMy4xNDEgMjMuMTQxIDAgMCAxIC03LjY4Mi0xLjA4NiA1LjAxOSA1LjAxOSAwIDAgMSAtLjktLjQzOCAyLjMzNCAyLjMzNCAwIDAgMSAtLjMxOC0uMzE4IDEuNzcgMS43NyAwIDAgMSAuMjMyLS4yMDl6bS0uMyAyLjY0M2MxLjM4Ny43IDQuMDA1IDEuNDA4IDguOTY4IDEuNDA4czcuNTc1LS43IDguOTY0LTEuNDA2djMuMTEyYTIuMzE2IDIuMzE2IDAgMCAxIC0uNzM3LjY0N2MtMS4xNzQuNjY0LTMuNTMgMS4yNzktOC4yMjcgMS4yNzktNC42NjkgMC03LjAyOC0uNjE2LTguMjEyLTEuMjhhMi40NTUgMi40NTUgMCAwIDEgLS43NS0uNjQ2cTAtLjAzNi0uMDEyLS4wNzJ6bTguOTc2IDE2LjMzN2MtNC42NyAwLTcuMDI5LS42MTctOC4yMTMtMS4yODFhMi40MzggMi40MzggMCAwIDEgLS43NjItLjY2MmwuMDA2LTMuMWMxLjM4OC43IDQuMDA2IDEuNDA4IDguOTY5IDEuNDA4IDEuMTE3IDAgMi4xMTItLjAzNSAzLS4xLS4wMTEuMTY3LS4wMTYuMzM1LS4wMTYuNTA1YTcuOTM4IDcuOTM4IDAgMCAwIC42MTUgMy4wNzJjLTEuMDE4LjEwMy0yLjIwOC4xNTgtMy41OTkuMTU4em0zLjQtNS43OGMtLjk3NS4wODUtMi4xLjEzNC0zLjQuMTM0LTQuNjcgMC03LjAyOS0uNjE2LTguMjEzLTEuMjhhMi40MjkgMi40MjkgMCAwIDEgLS43NjEtLjY2MmwuMDA2LTMuMWMxLjM4OC43IDQuMDA2IDEuNDA4IDguOTY5IDEuNDA4YTMwLjIyMyAzMC4yMjMgMCAwIDAgNi41MzktLjU4NSA4LjAyIDguMDIgMCAwIDAgLTMuMTQgNC4wODZ6bTcuNTgxIDguNTRhNS45ODggNS45ODggMCAwIDEgLTUuODUxLTcuMzE1Yy4wMDYtLjAxOS4wMTEtLjAzOS4wMTYtLjA1OHMuMDA5LS4wMzkuMDEyLS4wNTlhNiA2IDAgMSAxIDUuODIzIDcuNDMyeiIvPg0KICAgIDxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0ibTE5LjYzOSAyMS45YTEgMSAwIDAgMCAxLjQxOCAxLjQxMSAyLjYyOCAyLjYyOCAwIDAgMSAzLjcyNSAwIDEgMSAwIDAgMCAxLjQxOC0xLjQxMSA0LjYyOCA0LjYyOCAwIDAgMCAtNi41NjEgMHoiLz4NCiAgICA8ZWxsaXBzZSBmaWxsPSJjdXJyZW50Q29sb3IiIGN4PSIyMi45ODUiIGN5PSIyNC44NDMiIHJ4PSIxLjY2NyIgcnk9IjEuNjc0Ii8+DQogICAgPHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJtMjcuOTc5IDE5Ljc2OGE3LjA0NSA3LjA0NSAwIDAgMCAtOS45ODggMCAxIDEgMCAxIDAgMS40MTcgMS40MTIgNS4wNDUgNS4wNDUgMCAwIDEgNy4xNTQgMCAxIDEgMCAxIDAgMS40MTctMS40MTJ6Ii8+DQo8L3N2Zz4=", + "command": "test", + "jsonData": "{\"config\": {\"dbtype\": \"${data.dbtype}\", \"port\": \"${data.port}\", \"host\": \"${data.host}\", \"user\": \"${data.user}\", \"fileName\": \"${data.fileName}\", \"password\": \"${data.password}\", \"encrypt\": ${data.encrypt}, \"rejectUnauthorized\": ${data.rejectUnauthorized}}}", + "label": "Test connection" + }, - "_resetDB": { - "type": "sendTo", - "variant": "outlined", - "color": "primary", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjODAxMTExIiBkPSJNNiAxOWMwIDEuMS45IDIgMiAyaDhjMS4xIDAgMi0uOSAyLTJWN0g2djEyek0xOSA0aC0zLjVsLTEtMWgtNWwtMSAxSDV2MmgxNFY0eiIvPjwvc3ZnPgkJCQkJCQkJ", - "disabled": "!_alive", - "result": { - "deleted": "Deleted! Restarting..." - }, - "confirm": { - "text": "Are you sure? All data will be dropped.", - "title": "Please confirm", - "ok": "Delete", - "cancel": "Cancel" - }, - "command": "destroy", - "label": "Reset DB" - }, + "_resetDB": { + "type": "sendTo", + "variant": "outlined", + "color": "primary", + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjODAxMTExIiBkPSJNNiAxOWMwIDEuMS45IDIgMiAyaDhjMS4xIDAgMi0uOSAyLTJWN0g2djEyek0xOSA0aC0zLjVsLTEtMWgtNWwtMSAxSDV2MmgxNFY0eiIvPjwvc3ZnPgkJCQkJCQkJ", + "disabled": "!_alive", + "result": { + "deleted": "Deleted! Restarting..." + }, + "confirm": { + "text": "Are you sure? All data will be dropped.", + "title": "Please confirm", + "ok": "Delete", + "cancel": "Cancel" + }, + "command": "destroy", + "label": "Reset DB" + }, - "writeNulls": { - "newLine": true, - "type": "checkbox", - "label": "Write NULL values on start/stop boundaries", - "sm": 12 - } - } - }, - "defaultTab": { - "type": "panel", - "label": "Default settings", - "items": { - "debounceTime": { - "newLine": true, - "type": "number", - "sm": 12, - "md": 4, - "min": 0, - "max": 86400000, - "label": "De-bounce time", - "help": "De-bounce interval(ms)" - }, - "blockTime": { - "type": "number", - "sm": 12, - "md": 4, - "min": 0, - "max": 86400000, - "label": "Block time", - "defaultFunc": "(data.debounce || data.debounce === '0' || data.debounce === 0) ? parseInt(data.debounce, 10) : 0", - "help": "blockTime" - }, - "changesRelogInterval": { - "newLine": true, - "type": "number", - "label": "log changes interval(s)", - "help": "0 = disable", - "sm": 12, - "md": 6, - "lg": 4 - }, - "changesMinDelta": { - "type": "number", - "label": "log changes minimal delta", - "help": "0 = disable delta check", - "sm": 12, - "md": 6, - "lg": 4 - }, - "retention": { - "newLine": true, - "type": "select", - "label": "retention", - "options": [ - { - "value": 0, - "label": "keep forever" - }, - { - "value": 63072000, - "label": "2 years" - }, - { - "value": 31536000, - "label": "1 year" - }, - { - "value": 15811200, - "label": "6 months" - }, - { - "value": 7948800, - "label": "3 months" - }, - { - "value": 2678400, - "label": "1 months" - }, - { - "value": 1209600, - "label": "2 weeks" - }, - { - "value": 604800, - "label": "1 week" - }, - { - "value": 432000, - "label": "5 days" - }, - { - "value": 259200, - "label": "3 days" - }, - { - "value": 86400, - "label": "1 day" - }, - { - "value": -1, - "label": "Custom timeframe" + "writeNulls": { + "newLine": true, + "type": "checkbox", + "label": "Write NULL values on start/stop boundaries", + "sm": 12 + } } - ], - "sm": 12, - "md": 6, - "lg": 4 }, - "customRetentionDuration": { - "type": "number", - "label": "Custom retention duration (days)", - "min": 1, - "sm": 12, - "md": 4, - "hidden": "(data.retention !== -1)", - "help": "Number of days to keep the data." - }, - "maxLength": { - "newLine": true, - "type": "number", - "label": "maximum datapoint count in RAM", - "min": 0, - "max": 100000, - "sm": 12, - "md": 4 - }, - "round": { - "newLine": true, - "type": "text", - "label": "Round real to", - "sm": 12, - "md": 4, - "lg": 3, - "validator": "((data.round || '').toString().length === 0) || isFinite(data.round)", - "validatorErrorText": "Enter a number or leave the field empty", - "validatorNoSaveOnError": true - }, - "writeNulls": { - "newLine": true, - "label": "Write NULL values on start/stop boundaries", - "type": "checkbox", - "sm": 12, - "md": 4 - }, - "disableSkippedValueLogging": { - "label": "Disable charting optimized logging of skipped values", - "type": "checkbox", - "sm": 12, - "md": 4 + "defaultTab": { + "type": "panel", + "label": "Default settings", + "items": { + "debounceTime": { + "newLine": true, + "type": "number", + "sm": 12, + "md": 4, + "min": 0, + "max": 86400000, + "label": "De-bounce time", + "help": "De-bounce interval(ms)" + }, + "blockTime": { + "type": "number", + "sm": 12, + "md": 4, + "min": 0, + "max": 86400000, + "label": "Block time", + "defaultFunc": "(data.debounce || data.debounce === '0' || data.debounce === 0) ? parseInt(data.debounce, 10) : 0", + "help": "blockTime" + }, + "changesRelogInterval": { + "newLine": true, + "type": "number", + "label": "log changes interval(s)", + "help": "0 = disable", + "sm": 12, + "md": 6, + "lg": 4 + }, + "changesMinDelta": { + "type": "number", + "label": "log changes minimal delta", + "help": "0 = disable delta check", + "sm": 12, + "md": 6, + "lg": 4 + }, + "retention": { + "newLine": true, + "type": "select", + "label": "retention", + "options": [ + { + "value": 0, + "label": "keep forever" + }, + { + "value": 63072000, + "label": "2 years" + }, + { + "value": 31536000, + "label": "1 year" + }, + { + "value": 15811200, + "label": "6 months" + }, + { + "value": 7948800, + "label": "3 months" + }, + { + "value": 2678400, + "label": "1 months" + }, + { + "value": 1209600, + "label": "2 weeks" + }, + { + "value": 604800, + "label": "1 week" + }, + { + "value": 432000, + "label": "5 days" + }, + { + "value": 259200, + "label": "3 days" + }, + { + "value": 86400, + "label": "1 day" + }, + { + "value": -1, + "label": "Custom timeframe" + } + ], + "sm": 12, + "md": 6, + "lg": 4 + }, + "customRetentionDuration": { + "type": "number", + "label": "Custom retention duration (days)", + "min": 1, + "sm": 12, + "md": 4, + "hidden": "(data.retention !== -1)", + "help": "Number of days to keep the data." + }, + "maxLength": { + "newLine": true, + "type": "number", + "label": "maximum datapoint count in RAM", + "min": 0, + "max": 100000, + "sm": 12, + "md": 4 + }, + "round": { + "newLine": true, + "type": "text", + "label": "Round real to", + "sm": 12, + "md": 4, + "lg": 3, + "validator": "((data.round || '').toString().length === 0) || isFinite(data.round)", + "validatorErrorText": "Enter a number or leave the field empty", + "validatorNoSaveOnError": true + }, + "writeNulls": { + "newLine": true, + "label": "Write NULL values on start/stop boundaries", + "type": "checkbox", + "sm": 12, + "md": 4 + }, + "disableSkippedValueLogging": { + "label": "Disable charting optimized logging of skipped values", + "type": "checkbox", + "sm": 12, + "md": 4 + }, + "enableDebugLogs": { + "newLine": true, + "label": "Enable enhanced debug logs for the datapoint", + "type": "checkbox", + "sm": 12, + "md": 4 + }, + "debounce": { + "type": "number", + "sm": 12, + "md": 4, + "min": 0, + "label": "De-bounce time", + "help": "debounce", + "hidden": "true" + } + } }, - "enableDebugLogs": { - "newLine": true, - "label": "Enable enhanced debug logs for the datapoint", - "type": "checkbox", - "sm": 12, - "md": 4 + "dockerMysql": { + "type": "panel", + "label": "MySQL Docker", + "hidden": "!data.dockerMysql.?enabled", + "items": { + "dockerMysql.bind": { + "newLine": true, + "listenOnAllPorts": true, + "type": "ip", + "label": "Start container on IP", + "sm": 12, + "md": 8 + }, + "dockerMysql.port": { + "type": "port", + "label": "Port", + "help": "The port on which the container will be reachable on localhost", + "default": 3306, + "sm": 12, + "md": 4 + }, + "dockerMysql.stopIfInstanceStopped": { + "newLine": true, + "type": "checkbox", + "label": "Stop container when instance stops", + "sm": 12, + "md": 6 + }, + "dockerMysql.autoImageUpdate": { + "type": "checkbox", + "label": "Auto-update Docker image", + "help": "If enabled, the latest Docker image will be pulled when the adapter starts.", + "sm": 12, + "md": 6 + }, + "dockerMysql.rootPassword": { + "newLine": true, + "type": "password", + "label": "Root password", + "xs": 12 + } + } }, - "debounce": { - "type": "number", - "sm": 12, - "md": 4, - "min": 0, - "label": "De-bounce time", - "help": "debounce", - "hidden": "true" + "dockerPhpMyAdmin": { + "type": "panel", + "label": "Grafana Docker", + "hidden": "!data.dockerPhpMyAdmin.?enabled || !data.dockerMysql.?enabled", + "items": { + "dockerPhpMyAdmin.bind": { + "type": "ip", + "label": "Start container on IP", + "listenOnAllPorts": true, + "sm": 12, + "md": 8 + }, + "dockerPhpMyAdmin.port": { + "type": "port", + "label": "Port", + "help": "The port on which the container will be reachable on localhost", + "default": 8080, + "sm": 12, + "md": 4 + }, + "dockerPhpMyAdmin.stopIfInstanceStopped": { + "newLine": true, + "type": "checkbox", + "hidden": "!!data.dockerMysql?.stopIfInstanceStopped", + "label": "Stop container when instance stops", + "sm": 12, + "md": 6 + }, + "dockerPhpMyAdmin.autoImageUpdate": { + "type": "checkbox", + "label": "Auto-update Docker image", + "help": "If enabled, the latest Docker image will be pulled when the adapter starts.", + "sm": 12, + "md": 6 + }, + "dockerPhpMyAdmin.absoluteUri": { + "newLine": true, + "type": "text", + "label": "Root URL", + "help": "Defines the root URL, useful when PhpMyAdmin is behind a reverse proxy.", + "xs": 12 + } + } } - } } - } } diff --git a/build/lib/DockerManager.js b/build/lib/DockerManager.js new file mode 100644 index 00000000..f6302548 --- /dev/null +++ b/build/lib/DockerManager.js @@ -0,0 +1,2277 @@ +"use strict"; +// This class implements docker commands using CLI and +// it monitors periodically the docker daemon status. +// It manages containers defined in adapter.config.containers and monitors other containers +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +var _a; +Object.defineProperty(exports, "__esModule", { value: true }); +const node_util_1 = require("node:util"); +const node_child_process_1 = require("node:child_process"); +const node_net_1 = require("node:net"); +const dockerode_1 = __importDefault(require("dockerode")); +const execPromise = (0, node_util_1.promisify)(node_child_process_1.exec); +const dockerDefaults = { + tty: false, + stdinOpen: false, + attachStdin: false, + attachStdout: false, + attachStderr: false, + openStdin: false, + publishAllPorts: false, + readOnly: false, + user: '', + workdir: '', + domainname: '', + macAddress: '', + networkMode: 'bridge', +}; +function isDefault(value, def) { + return JSON.stringify(value) === JSON.stringify(def); +} +function deepCompare(object1, object2) { + if (typeof object1 === 'number') { + object1 = object1.toString(); + } + if (typeof object2 === 'number') { + object2 = object2.toString(); + } + if (typeof object1 !== typeof object2) { + return false; + } + if (typeof object1 !== 'object' || object1 === null || object2 === null) { + return object1 === object2; + } + if (Array.isArray(object1)) { + if (!Array.isArray(object2) || object1.length !== object2.length) { + return false; + } + for (let i = 0; i < object1.length; i++) { + if (!deepCompare(object1[i], object2[i])) { + return false; + } + } + return true; + } + const keys1 = Object.keys(object1); + for (const key of keys1) { + // ignore iob* properties as they belong to ioBroker configuration + // ignore hostname + if (key.startsWith('iob') || key === 'hostname') { + continue; + } + if (!deepCompare(object1[key], object2[key])) { + return false; + } + } + return true; +} +function compareConfigs(desired, existing) { + const diffs = []; + const keys = Object.keys(desired); + // We only compare keys that are in the desired config + for (const key of keys) { + // ignore iob* properties as they belong to ioBroker configuration + // ignore hostname + if (key.startsWith('iob') || key === 'hostname') { + continue; + } + if (typeof desired[key] === 'object' && desired[key] !== null) { + if (Array.isArray(desired[key])) { + if (!Array.isArray(existing[key]) || desired[key].length !== existing[key].length) { + diffs.push(key); + } + else { + for (let i = 0; i < desired[key].length; i++) { + if (!deepCompare(desired[key][i], existing[key][i])) { + diffs.push(`${key}[${i}]`); + } + } + } + } + else { + Object.keys(desired[key]).forEach((subKey) => { + if (!deepCompare(desired[key][subKey], existing[key][subKey])) { + diffs.push(`${key}.${subKey}`); + } + }); + } + } + else if (desired[key] !== existing[key]) { + diffs.push(key); + } + } + return diffs; +} +// remove undefined entries recursively +function removeUndefined(obj) { + if (Array.isArray(obj)) { + const arr = obj.map(v => (v && typeof v === 'object' ? removeUndefined(v) : v)).filter(v => v !== undefined); + if (!arr.length) { + return undefined; + } + return arr; + } + if (obj && typeof obj === 'object') { + const _obj = Object.fromEntries(Object.entries(obj) + .map(([k, v]) => [k, v && typeof v === 'object' ? removeUndefined(v) : v]) + .filter(([_, v]) => v !== undefined && + v !== null && + v !== '' && + !(Array.isArray(v) && v.length === 0) && + !(typeof v === 'object' && Object.keys(v).length === 0))); + if (Object.keys(_obj).length === 0) { + return undefined; + } + return _obj; + } + if (obj === '') { + return undefined; + } + return obj; +} +function cleanContainerConfig(obj, mayChange) { + obj = removeUndefined(obj); + Object.keys(obj).forEach(name => { + if (isDefault(obj[name], dockerDefaults[name])) { + delete obj[name]; + } + if (name === 'mounts') { + if (!obj.mounts) { + delete obj.mounts; + return; + } + obj.mounts = obj.mounts.map((mount) => { + const m = { ...mount }; + // /var/lib/docker/volumes/influxdb_0_flux_config/_data + if (mayChange && m.source.includes('/docker/volumes') && m.source.endsWith('/_data')) { + const parts = m.source.split('/'); + m.source = parts[parts.length - 2]; + } + delete m.readOnly; + return m; + }); + if (!obj.mounts.length) { + delete obj.mounts; + return; + } + obj.mounts?.sort((a, b) => a.target.localeCompare(b.target)); + } + if (name === 'ports') { + if (!obj.ports) { + delete obj.ports; + return; + } + obj.ports = obj.ports.map((port) => { + const p = { ...port }; + if (p.protocol === 'tcp') { + delete p.protocol; + } + return p; + }); + if (!obj.ports.length) { + delete obj.ports; + return; + } + obj.ports?.sort((a, b) => { + if (a.hostPort !== b.hostPort) { + return parseInt(a.containerPort, 10) - parseInt(b.containerPort, 10); + } + if (a.hostIP !== b.hostIP && a.hostIP && b.hostIP) { + return a.hostIP?.localeCompare(b.hostIP); + } + return 0; + }); + } + if (name === 'environment') { + if (!obj.environment) { + delete obj.environment; + return; + } + const env = obj.environment; + if (Object.keys(env).length) { + obj.environment = {}; + Object.keys(env) + .sort() + .forEach(key => { + if (key && env[key]) { + obj.environment[key] = env[key]; + } + }); + } + else { + delete obj.environment; + } + if (!Object.keys(env).length) { + delete obj.environment; + } + } + if (name === 'labels') { + if (!obj.labels) { + delete obj.labels; + return; + } + const labels = obj.labels; + if (Object.keys(labels).length) { + obj.labels = {}; + Object.keys(labels) + .sort() + .forEach(key => { + if (key && labels[key]) { + obj.labels[key] = labels[key]; + } + }); + } + else { + delete obj.labels; + } + if (!Object.keys(labels).length) { + delete obj.labels; + } + } + if (name === 'volumes') { + if (!obj.volumes?.length) { + delete obj.volumes; + return; + } + obj.volumes = obj.volumes.map(v => v.trim()).filter(v => v); + obj.volumes.sort(); + if (!obj.volumes?.length) { + delete obj.volumes; + } + } + }); + obj.volumes?.sort(); + return obj; +} +function size2string(size) { + if (size < 1024) { + return `${size} B`; + } + if (size < 1024 * 1024) { + return `${(size / 1024).toFixed(2)} KB`; + } + if (size < 1024 * 1024 * 1024) { + return `${(size / (1024 * 1024)).toFixed(2)} MB`; + } + return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`; +} +class DockerManager { + installed = false; + dockerVersion = ''; + needSudo = false; + #waitReady; + #waitAllChecked; + #waitAllCheckedResolve; + adapter; + #ownContainers = []; + #monitoringInterval = null; + #ownContainersStats = {}; + #driver = 'cli'; + #dockerode = null; + #cliAvailable = false; + options; + #tarPack = null; + constructor(adapter, options, containers) { + this.adapter = adapter; + this.options = options || {}; + this.#ownContainers = containers || []; + this.#waitReady = new Promise(resolve => this.#init().then(() => resolve())); + this.#waitAllChecked = new Promise(resolve => (this.#waitAllCheckedResolve = resolve)); + } + /** Wait till the check if docker is installed and the daemon is running is ready */ + isReady() { + return this.#waitReady; + } + /** + * Convert information from inspect to docker configuration to start it + * + * @param inspect Inspect information + */ + static mapInspectToConfig(inspect) { + const obj = { + image: inspect.Config.Image, + name: inspect.Name.replace(/^\//, ''), + command: inspect.Config.Cmd ?? undefined, + entrypoint: inspect.Config.Entrypoint ?? undefined, + user: inspect.Config.User ?? undefined, + workdir: inspect.Config.WorkingDir ?? undefined, + hostname: inspect.Config.Hostname ?? undefined, + domainname: inspect.Config.Domainname ?? undefined, + macAddress: inspect.NetworkSettings.MacAddress ?? undefined, + environment: inspect.Config.Env + ? Object.fromEntries(inspect.Config.Env.map(e => { + const [key, ...rest] = e.split('='); + return [key, rest.join('=')]; + })) + : undefined, + labels: inspect.Config.Labels ?? undefined, + tty: inspect.Config.Tty, + stdinOpen: inspect.Config.OpenStdin, + attachStdin: inspect.Config.AttachStdin, + attachStdout: inspect.Config.AttachStdout, + attachStderr: inspect.Config.AttachStderr, + openStdin: inspect.Config.OpenStdin, + publishAllPorts: inspect.HostConfig.PublishAllPorts, + ports: inspect.HostConfig.PortBindings + ? Object.entries(inspect.HostConfig.PortBindings).flatMap(([containerPort, bindings]) => bindings.map(binding => ({ + containerPort: containerPort.split('/')[0], + protocol: containerPort.split('/')[1] || 'tcp', + hostPort: binding.HostPort, + hostIP: binding.HostIp, + }))) + : undefined, + mounts: inspect.Mounts?.map(mount => ({ + type: mount.Type, + source: mount.Source, + target: mount.Destination, + readOnly: mount.RW, + })), + volumes: inspect.Config.Volumes ? Object.keys(inspect.Config.Volumes) : undefined, + extraHosts: inspect.HostConfig.ExtraHosts ?? undefined, + dns: { + servers: inspect.HostConfig.Dns, + search: inspect.HostConfig.DnsSearch, + options: inspect.HostConfig.DnsOptions, + }, + networkMode: inspect.HostConfig.NetworkMode, + networks: inspect.NetworkSettings.Networks + ? Object.entries(inspect.NetworkSettings.Networks).map(([name, net]) => ({ + name, + aliases: net.Aliases ?? undefined, + ipv4Address: net.IPAddress, + ipv6Address: net.GlobalIPv6Address, + driverOpts: net.DriverOpts ?? undefined, + })) + : undefined, + restart: { + policy: inspect.HostConfig.RestartPolicy.Name, + maxRetries: inspect.HostConfig.RestartPolicy.MaximumRetryCount, + }, + resources: { + cpuShares: inspect.HostConfig.CpuShares, + cpuQuota: inspect.HostConfig.CpuQuota, + cpuPeriod: inspect.HostConfig.CpuPeriod, + cpusetCpus: inspect.HostConfig.CpusetCpus, + memory: inspect.HostConfig.Memory, + memorySwap: inspect.HostConfig.MemorySwap, + memoryReservation: inspect.HostConfig.MemoryReservation, + pidsLimit: inspect.HostConfig.PidsLimit ?? undefined, + shmSize: inspect.HostConfig.ShmSize, + readOnlyRootFilesystem: inspect.HostConfig.ReadonlyRootfs, + }, + logging: { + driver: inspect.HostConfig.LogConfig.Type, + options: inspect.HostConfig.LogConfig.Config, + }, + security: { + privileged: inspect.HostConfig.Privileged, + capAdd: inspect.HostConfig.CapAdd ?? undefined, + capDrop: inspect.HostConfig.CapDrop ?? undefined, + usernsMode: inspect.HostConfig.UsernsMode ?? undefined, + ipc: inspect.HostConfig.IpcMode, + pid: inspect.HostConfig.PidMode, + seccomp: inspect.HostConfig.SecurityOpt?.find(opt => opt.startsWith('seccomp='))?.split('=')[1] ?? undefined, + apparmor: inspect.HostConfig.SecurityOpt?.find(opt => opt.startsWith('apparmor='))?.split('=')[1] ?? + undefined, + groupAdd: inspect.HostConfig.GroupAdd ?? undefined, + noNewPrivileges: undefined, // Nicht direkt verfügbar + }, + sysctls: inspect.HostConfig.Sysctls ?? undefined, + init: inspect.HostConfig.Init ?? undefined, + stop: { + signal: inspect.Config.StopSignal ?? undefined, + gracePeriodSec: inspect.Config.StopTimeout ?? undefined, + }, + readOnly: inspect.HostConfig.ReadonlyRootfs, + timezone: undefined, // Nicht direkt verfügbar + __meta: undefined, // Eigene Metadaten + }; + return cleanContainerConfig(obj, true); + } + /** + * Get information about the Docker daemon: is it running and which version + * + * @returns Object with version and daemonRunning + */ + async getDockerDaemonInfo() { + await this.isReady(); + const daemonRunning = await this.#isDockerDaemonRunning(); + return { + version: this.dockerVersion, + daemonRunning, + removeSupported: !this.#dockerode || this.#cliAvailable, + driver: this.#driver, + }; + } + static checkDockerSocket() { + return new Promise(resolve => { + const socket = (0, node_net_1.createConnection)({ path: '/var/run/docker.sock' }, () => { + socket.end(); + resolve(true); + }); + socket.on('error', e => { + console.error(`Cannot connect to docker socket: ${e.message}`); + resolve(false); + }); + }); + } + static async isDockerApiRunningOnPort(port, host = 'localhost') { + return new Promise(resolve => { + const socket = (0, node_net_1.createConnection)({ port, host }, () => { + socket.write('GET /version HTTP/1.0\r\nHost: localhost\r\n\r\n'); + }); + let data = ''; + socket.on('data', chunk => (data += chunk.toString())); + socket.on('end', () => { + resolve(data.includes('Docker') || data.includes('Api-Version')); + }); + socket.on('error', () => resolve(false)); + }); + } + async #init() { + // first of all detects which way is available: + // - '/var/run/docker.sock', + // - http://localhost:2375, + // - https://localhost:2376 or + // - CLI + // Probe the socket + if (this.options?.dockerApi && this.options.dockerApiHost && this.options.dockerApiPort) { + this.#driver = this.options.dockerApiProtocol || 'http'; + this.#dockerode = new dockerode_1.default({ + host: this.options.dockerApiHost, + port: this.options.dockerApiPort, + protocol: this.options.dockerApiProtocol || 'http', + }); + } + else if (await _a.checkDockerSocket()) { + this.#driver = 'socket'; + this.#dockerode = new dockerode_1.default({ socketPath: '/var/run/docker.sock' }); + } + else if (await _a.isDockerApiRunningOnPort(2375)) { + this.#driver = 'http'; + this.#dockerode = new dockerode_1.default({ protocol: 'http', host: '127.0.0.1', port: 2375 }); + } + else if (await _a.isDockerApiRunningOnPort(2376)) { + this.#driver = 'http'; + this.#dockerode = new dockerode_1.default({ protocol: 'http', host: '127.0.0.1', port: 2376 }); + } + else { + this.#driver = 'cli'; + this.#cliAvailable = true; + } + if (!this.#cliAvailable) { + try { + const result = await execPromise('docker --version'); + if (!result.stderr && result.stdout) { + this.#cliAvailable = true; + } + } + catch { + // ignore + } + } + const version = await this.#isDockerInstalled(); + this.installed = !!version; + if (version) { + this.dockerVersion = version; + } + else { + const daemonRunning = await this.#isDockerDaemonRunning(); + if (daemonRunning) { + // Docker daemon is running, but docker command not found + this.adapter.log.warn('Docker daemon is running, but docker command not found. May be "iobroker" user has no access to Docker. Run "iob fix" command to fix it.'); + } + else { + this.adapter.log.warn('Docker is not installed. Please install Docker.'); + } + } + if (this.installed) { + // we still must check the sudo as autocompletion works only via CLI + this.needSudo = await this.#isNeedSudo(); + await this.#checkOwnContainers(); + } + else { + this.#waitAllCheckedResolve?.(); + } + } + async #isDockerDaemonRunning() { + if (this.#dockerode) { + return true; + } + try { + const { stdout, stderr } = await execPromise('systemctl status docker'); + // ● docker.service - Docker Application Container Engine + // Loaded: loaded (/lib/systemd/system/docker.service; enabled; preset: enabled) + // Active: active (running) since Fri 2025-08-15 08:37:22 CEST; 3 weeks 2 days ago + // TriggeredBy: ● docker.socket + // Docs: https://docs.docker.com + // Main PID: 785 (dockerd) + // Tasks: 30 + // CPU: 4min 17.003s + // CGroup: /system.slice/docker.service + // ├─ 785 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock + // ├─97032 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 5000 -container-ip 172.17.0.2 -container-port 5000 -use-listen-fd + // └─97039 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 5000 -container-ip 172.17.0.2 -container-port 5000 -use-listen-fd + if (stderr?.includes('could not be found') || stderr.includes('not-found')) { + this.adapter.log.error(`Docker is not installed: ${stderr}`); + return false; + } + return stdout.includes('(running)'); + } + catch { + return false; + } + } + /** + * Ensure that the given container is running with the actual configuration + * + * @param container Container configuration + */ + async #ensureActualConfiguration(container) { + if (!container.name) { + throw new Error(`Container name must be a string, but got boolean true`); + } + // Check the configuration of the container + const inspect = await this.containerInspect(container.name); + if (inspect) { + const existingConfig = _a.mapInspectToConfig(inspect); + console.log('Compare existing config', existingConfig, ' and', container); + container = cleanContainerConfig(container); + const diffs = compareConfigs(container, existingConfig); + if (diffs.length) { + this.adapter.log.info(`Configuration of own container ${container.name} has changed: ${diffs.join(', ')}. Restarting container...`); + const result = await this.containerReCreate(container); + if (result.stderr) { + this.adapter.log.warn(`Cannot recreate own container ${container.name}: ${result.stderr}`); + } + } + else { + this.adapter.log.debug(`Configuration of own container ${container.name} is up to date`); + } + // Check if container is running + const status = await this.containerList(true); + const containerInfo = status.find(it => it.names === container.name); + if (containerInfo) { + if (containerInfo.status !== 'running' && containerInfo.status !== 'restarting') { + // Start the container + this.adapter.log.info(`Starting own container ${container.name}`); + try { + const result = await this.containerStart(containerInfo.id); + if (result.stderr) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${result.stderr}`); + } + } + catch (e) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${e.message}`); + } + } + else { + this.adapter.log.debug(`Own container ${container.name} is already running`); + } + } + else { + this.adapter.log.warn(`Own container ${container.name} not found in container list after recreation`); + } + } + } + getDefaultContainerName() { + return `iob_${this.adapter.namespace.replace(/[-.]/g, '_')}`; + } + async #checkOwnContainers() { + if (!this.#ownContainers.length) { + this.#waitAllCheckedResolve?.(); + return; + } + const status = await this.containerList(true); + let images = await this.imageList(); + let anyStartedOrRunning = false; + const networkChecked = []; + const prefix = this.getDefaultContainerName(); + for (let c = 0; c < this.#ownContainers.length; c++) { + const container = this.#ownContainers[c]; + if (container.iobEnabled !== false) { + if (!container.image.includes(':')) { + container.image += ':latest'; + } + if (container.labels?.iobroker !== this.adapter.namespace) { + container.labels = { ...container.labels, iobroker: this.adapter.namespace }; + } + container.name ||= prefix; + // Name of the container, name of the network and name of the volume must start with iob___ + if (container.name !== prefix && !container.name.startsWith(`${prefix}_`)) { + this.adapter.log.debug(`Renaming container ${container.name} to be prefixed with iob_${prefix}_`); + container.name = `${prefix}_${container.name}`; + } + try { + // create iobroker network if necessary + if (container.networkMode && + container.networkMode !== 'container' && + container.networkMode !== 'host' && + container.networkMode !== 'bridge' && + container.networkMode !== 'none') { + if (container.networkMode === true) { + container.networkMode = prefix; + } + if (container.networkMode !== prefix && !container.networkMode.startsWith(`${prefix}_`)) { + this.adapter.log.debug(`Renaming network ${container.networkMode} to be prefixed with ${prefix}_`); + container.networkMode = `${prefix}_${container.networkMode}`; + } + if (!networkChecked.includes(container.networkMode)) { + // check if the network exists + const networks = await this.networkList(); + if (!networks.find(it => it.name === container.networkMode)) { + this.adapter.log.info(`Creating docker network ${container.networkMode}`); + await this.networkCreate(container.networkMode); + } + networkChecked.push(container.networkMode); + } + } + // create all volumes ourselves, to have a static name + if (container.mounts?.find(m => m.type === 'volume')) { + // check if the volume exists + const volumes = await this.volumeList(); + for (const mount of container.mounts) { + if (mount.type === 'volume' && mount.source) { + if (mount.source === true) { + mount.source = prefix; + } + if (mount.source !== prefix && !mount.source.startsWith(`${prefix}_`)) { + this.adapter.log.debug(`Renaming volume ${mount.source} to be prefixed with ${prefix}_`); + mount.source = `${prefix}_${mount.source}`; + } + if (mount.iobBackup) { + if (!container.labels.iob_backup) { + container.labels = { ...container.labels, iob_backup: mount.source }; + } + else { + const volumes = container.labels.iob_backup + .split(',') + .map(v => v.trim()) + .filter(v => v); + if (!volumes.includes(mount.source)) { + volumes.push(mount.source); + container.labels = { ...container.labels, iob_backup: volumes.join(',') }; + } + } + } + const volume = volumes.find(v => v.name === mount.source); + if (!volume) { + this.adapter.log.info(`Creating docker volume ${mount.source}`); + const result = await this.volumeCreate(mount.source); + if (result.stderr) { + this.adapter.log.warn(`Cannot create volume ${mount.source}: ${result.stderr}`); + continue; + } + // Copy data from host to volume + if (mount.iobAutoCopyFrom) { + await this.volumeCopyTo(mount.source, mount.iobAutoCopyFrom); + } + } + else if (mount.iobAutoCopyFromForce && mount.iobAutoCopyFrom) { + // Copy data from host to volume + await this.volumeCopyTo(mount.source, mount.iobAutoCopyFrom); + } + } + } + } + let containerInfo = status.find(it => it.names === container.name); + let image = images.find(it => `${it.repository}:${it.tag}` === container.image); + if (container.iobAutoImageUpdate) { + // ensure that the image is actual + const newImage = await this.imageUpdate(container.image, true); + if (newImage) { + this.adapter.log.info(`Image ${container.image} for own container ${container.name} was updated`); + if (containerInfo) { + // destroy current container + await this.containerRemove(containerInfo.id); + containerInfo = undefined; + } + image = newImage; + } + } + if (!image) { + this.adapter.log.info(`Pulling image ${container.image} for own container ${container.name}`); + try { + const result = await this.imagePull(container.image); + if (result.stderr) { + this.adapter.log.warn(`Cannot pull image ${container.image}: ${result.stderr}`); + continue; + } + } + catch (e) { + this.adapter.log.warn(`Cannot pull image ${container.image}: ${e.message}`); + continue; + } + // Check that image is available now + images = await this.imageList(); + image = images.find(it => `${it.repository}:${it.tag}` === container.image); + if (!image) { + this.adapter.log.warn(`Image ${container.image} for own container ${container.name} not found after pull`); + continue; + } + } + if (containerInfo) { + await this.#ensureActualConfiguration(container); + anyStartedOrRunning ||= !!container.iobMonitoringEnabled; + } + else { + // Create and start the container, as the container was not found + this.adapter.log.info(`Creating and starting own container ${container.name}`); + try { + const result = await this.containerRun(container); + if (result.stderr) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${result.stderr}`); + } + else { + anyStartedOrRunning ||= !!container.iobMonitoringEnabled; + } + } + catch (e) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${e.message}`); + } + } + } + catch (e) { + this.adapter.log.warn(`Cannot check own container ${container.name}: ${e.message}`); + } + } + } + if (anyStartedOrRunning) { + this.#monitoringInterval ||= setInterval(() => this.#monitorOwnContainers(), 60000); + } + this.#waitAllCheckedResolve?.(); + } + allOwnContainersChecked() { + return this.#waitAllChecked; + } + async #monitorOwnContainers() { + // get the status of containers + const containers = await this.containerList(); + // Check the status of own containers + for (let c = 0; c < this.#ownContainers.length; c++) { + const container = this.#ownContainers[c]; + if (container.iobEnabled !== false && container.iobMonitoringEnabled && container.name) { + // Check if container is running + const running = containers.find(it => it.names === container.name); + if (!running || (running.status !== 'running' && running.status !== 'restarting')) { + this.adapter.log.warn(`Own container ${container.name} is not running. Restarting...`); + try { + const result = await this.containerStart(container.name); + if (result.stderr) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${result.stderr}`); + this.#ownContainersStats[container.name] = { + ...this.#ownContainersStats[container.name], + status: running?.status || 'unknown', + statusTs: Date.now(), + }; + continue; + } + } + catch (e) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${e.message}`); + this.#ownContainersStats[container.name] = { + ...this.#ownContainersStats[container.name], + status: running?.status || 'unknown', + statusTs: Date.now(), + }; + continue; + } + } + // check the stats + this.#ownContainersStats[container.name] = { + ...((await this.containerGetRamAndCpuUsage(container.name)) || {}), + status: running?.status || 'unknown', + statusTs: Date.now(), + }; + } + } + } + /** Read own container stats */ + getOwnContainerStats() { + return this.#ownContainersStats; + } + async containerGetRamAndCpuUsage(containerNameOrId) { + try { + const { stdout } = await this.#exec(`stats ${containerNameOrId} --no-stream --format "{{.CPUPerc}};{{.MemUsage}};{{.NetIO}};{{.BlockIO}};{{.PIDs}}"`); + // Example: "0.15%;12.34MiB / 512MiB;1.2kB / 2.3kB;0B / 0B;5" + const [cpuStr, memStr, netStr, blockIoStr, pid] = stdout.trim().split(';'); + const [memUsed, memMax] = memStr.split('/').map(it => it.trim()); + const [netRead, netWrite] = netStr.split('/').map(it => it.trim()); + const [blockIoRead, blockIoWrite] = blockIoStr.split('/').map(it => it.trim()); + return { + ts: Date.now(), + cpu: parseFloat(cpuStr.replace('%', '').replace(',', '.')), + memUsed: this.#parseSize(memUsed.replace('iB', 'B')), + memMax: this.#parseSize(memMax.replace('iB', 'B')), + netRead: this.#parseSize(netRead.replace('iB', 'B')), + netWrite: this.#parseSize(netWrite.replace('iB', 'B')), + processes: parseInt(pid, 10), + blockIoRead: this.#parseSize(blockIoRead.replace('iB', 'B')), + blockIoWrite: this.#parseSize(blockIoWrite.replace('iB', 'B')), + }; + } + catch (e) { + this.adapter.log.debug(`Cannot get stats: ${e.message}`); + return null; + } + } + /** + * Update the image if a newer version is available + * + * @param image Image name with tag + * @param ignoreIfNotExist If true, do not throw error if image does not exist + * @returns New image info if image was updated, null if no update was necessary + */ + async imageUpdate(image, ignoreIfNotExist) { + const list = await this.imageList(); + if (!image.includes(':')) { + image += ':latest'; + } + const existingImage = list.find(it => `${it.repository}:${it.tag}` === image); + if (!existingImage && !ignoreIfNotExist) { + throw new Error(`Image ${image} not found`); + } + // Pull the image + const result = await this.imagePull(image); + if (result.stderr) { + throw new Error(`Cannot pull image ${image}: ${result.stderr}`); + } + const newList = await this.imageList(); + const newImage = newList.find(it => `${it.repository}:${it.tag}` === image); + if (!newImage) { + throw new Error(`Image ${image} not found after pull`); + } + // If image ID has changed, image was updated + return !existingImage || existingImage.id !== newImage.id ? newImage : null; + } + #exec(command) { + if (!this.installed) { + return Promise.reject(new Error('Docker is not installed')); + } + const finalCommand = this.needSudo ? `sudo docker ${command}` : `docker ${command}`; + return execPromise(finalCommand); + } + async #isDockerInstalled() { + if (this.#driver === 'cli') { + try { + const result = await execPromise('docker --version'); + if (!result.stderr && result.stdout) { + // "Docker version 28.3.2, build 578ccf6\n" + return result.stdout.split('\n')[0].trim(); + } + this.adapter.log.debug(`Docker not installed: ${result.stderr}`); + } + catch (e) { + this.adapter.log.debug(`Docker not installed: ${e.message}`); + } + } + else if (this.#dockerode) { + try { + const info = await this.#dockerode.version(); + if (info?.Version) { + return `Docker version ${info.Version}, api version: ${info.ApiVersion}`; + } + } + catch (e) { + this.adapter.log.debug(`Docker not installed: ${e.message}`); + } + } + return false; + } + async #isNeedSudo() { + try { + await execPromise('docker ps'); + return false; + } + catch { + return true; + } + } + /** Get disk usage information */ + async discUsage() { + if (this.#dockerode) { + const info = await this.#dockerode.df(); + const result = { total: { size: 0, reclaimable: 0 } }; + if (info.Images) { + let size = 0; + let reclaimable = 0; + for (const image of info.Images) { + size += image.Size; + reclaimable += image.SharedSize + image.VirtualSize; + } + result.images = { + total: info.Images.length, + // @ts-expect-error todo + active: info.Images.filter(img => img.Containers > 0).length, + size, + reclaimable, + }; + result.total.size += size; + result.total.reclaimable += reclaimable; + } + if (info.Containers) { + let size = 0; + for (const container of info.Containers) { + size += container.SizeRootFs || 0; + } + result.containers = { + total: info.Containers.length, + // @ts-expect-error todo + active: info.Containers.filter(cont => cont.State === 'running').length, + size, + reclaimable: 0, // Not available + }; + result.total.size += size; + } + if (info.Volumes) { + let size = 0; + for (const volume of info.Volumes) { + size += volume.UsageData?.Size || 0; + } + result.volumes = { + total: info.Volumes.length, + // @ts-expect-error todo + active: info.Volumes.filter(vol => vol.UsageData?.RefCount && vol.UsageData.RefCount > 0).length, + size, + reclaimable: 0, // Not available + }; + result.total.size += size; + } + // Build cache not available + return result; + } + const { stdout } = await this.#exec(`system df`); + const result = { total: { size: 0, reclaimable: 0 } }; + // parse the output + // TYPE TOTAL ACTIVE SIZE RECLAIMABLE + // Images 2 1 2.715GB 2.715GB (99%) + // Containers 1 1 26.22MB 0B (0%) + // Local Volumes 0 0 0B 0B + // Build Cache 0 0 0B 0B + const lines = stdout.split('\n'); + for (const line of lines) { + const parts = line.trim().split(/\s+/); + if (parts.length >= 5 && parts[0] !== 'TYPE') { + let size; + let reclaimable; + if (parts[0] === 'Images') { + const sizeStr = parts[3]; + const reclaimableStr = parts[4].split(' ')[0]; + size = this.#parseSize(sizeStr); + reclaimable = this.#parseSize(reclaimableStr); + result.images = { + total: parseInt(parts[1], 10), + active: parseInt(parts[2], 10), + size, + reclaimable: reclaimable, + }; + } + else if (parts[0] === 'Containers') { + const sizeStr = parts[3]; + const reclaimableStr = parts[4].split(' ')[0]; + size = this.#parseSize(sizeStr); + reclaimable = this.#parseSize(reclaimableStr); + result.containers = { + total: parseInt(parts[1], 10), + active: parseInt(parts[2], 10), + size, + reclaimable: reclaimable, + }; + } + else if (parts[0] === 'Local' && parts[1] === 'Volumes') { + const sizeStr = parts[4]; + const reclaimableStr = parts[5].split(' ')[0]; + size = this.#parseSize(sizeStr); + reclaimable = this.#parseSize(reclaimableStr); + result.volumes = { + total: parseInt(parts[2], 10), + active: parseInt(parts[3], 10), + size, + reclaimable: reclaimable, + }; + } + else if (parts[0] === 'Build' && parts[1] === 'Cache') { + const sizeStr = parts[4]; + const reclaimableStr = parts[5].split(' ')[0]; + size = this.#parseSize(sizeStr); + reclaimable = this.#parseSize(reclaimableStr); + result.buildCache = { + total: parseInt(parts[2], 10), + active: parseInt(parts[3], 10), + size, + reclaimable: reclaimable, + }; + } + result.total.size += size || 0; + result.total.reclaimable += reclaimable || 0; + } + } + return result; + } + /** Pull an image from the registry */ + async imagePull(image) { + if (!image.includes(':')) { + image += ':latest'; + } + if (this.#dockerode) { + const stream = await this.#dockerode.pull(image); + if (!stream) { + throw new Error('No stream returned'); + } + return new Promise((resolve, reject) => { + const onFinished = (err) => { + if (err) { + return reject(err); + } + this.imageList() + .then(images => resolve({ stdout: `Image ${image} pulled`, stderr: '', images })) + .catch(reject); + }; + const lastShownProgress = {}; + const onProgress = (event) => { + // {"status":"Downloading","progressDetail":{"current":109494080,"total":689664036},"progress":"[=======> ] 109.5MB/689.7MB","id":"29bce3058cea"} + // {"status":"Download complete","progressDetail":{},"id":"6859c690a072"} + // {"status":"Verifying Checksum","progressDetail":{},"id":"6859c690a072"} + // {"status":"Extracting","progressDetail":{"current":32,"total":32},"progress":"[======>] 32B/32B","id":"4f4fb700ef54"} + // {"status":"Pull complete","progressDetail":{},"id":"4f4fb700ef54"} + if (!lastShownProgress || Date.now() - lastShownProgress[event.id] > 4000) { + if (event.status === 'Download complete' || + event.status === 'Pull complete' || + event.status === 'Verifying Checksum') { + this.adapter.log.debug(`Image ${image}/${event.id}: ${event.status}`); + } + else if (event.status === 'Downloading' || event.status === 'Extracting') { + this.adapter.log.debug(`Pulling image ${image}/${event.id}: ${event.status} ${Math.round((event.progressDetail.current / event.progressDetail.total) * 1000) / 10}% of ${size2string(event.progressDetail.total)}`); + } + else { + this.adapter.log.debug(`Pulling image ${image}/${event.id}: ${JSON.stringify(event)}`); + } + lastShownProgress[event.id] = Date.now(); + } + }; + this.#dockerode.modem.followProgress(stream, onFinished, onProgress); + }); + } + try { + const result = await this.#exec(`pull ${image}`); + const images = await this.imageList(); + if (!images.find(it => `${it.repository}:${it.tag}` === image)) { + throw new Error(`Image ${image} not found after pull`); + } + return { ...result, images }; + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** Autocomplete image names from Docker Hub */ + async imageNameAutocomplete(partialName) { + try { + // Read stars and descriptions + const { stdout } = await this.#exec(`search ${partialName} --format "{{.Name}};{{.Description}};{{.IsOfficial}};{{.StarCount}}" --limit 50`); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [name, description, isOfficial, starCount] = line.split(';'); + return { + name, + description, + isOfficial: isOfficial === 'true', + starCount: parseInt(starCount, 10) || 0, + }; + }); + } + catch (e) { + this.adapter.log.debug(`Cannot search images: ${e.message}`); + return []; + } + } + static getDockerodeConfig(config) { + let mounts; + if (config.mounts) { + for (const mount of config.mounts) { + let volumeOptions; + if (mount.volumeOptions) { + if (mount.volumeOptions.nocopy !== undefined) { + volumeOptions ||= {}; + volumeOptions.NoCopy = mount.volumeOptions.nocopy; + } + if (mount.volumeOptions.labels) { + volumeOptions ||= {}; + volumeOptions.Labels = mount.volumeOptions.labels; + } + } + let bindOptions; + if (mount.bindOptions) { + if (mount.bindOptions.propagation) { + bindOptions ||= {}; + bindOptions.Propagation = mount.bindOptions.propagation; + } + } + let tmpfsOptions; + if (mount.tmpfsOptions) { + if (mount.tmpfsOptions.size !== undefined) { + tmpfsOptions ||= {}; + tmpfsOptions.SizeBytes = mount.tmpfsOptions.size; + } + if (mount.tmpfsOptions.mode !== undefined) { + tmpfsOptions ||= {}; + tmpfsOptions.Mode = mount.tmpfsOptions.mode; + } + } + if (mount.source === true) { + throw new Error(`Mount source must be a string, but got boolean true`); + } + const m = { + Target: mount.target, + Source: mount.source || '', + Type: mount.type, + ReadOnly: mount.readOnly, + Consistency: mount.consistency, + VolumeOptions: volumeOptions, + BindOptions: bindOptions, + TmpfsOptions: tmpfsOptions, + }; + mounts ||= []; + mounts.push(m); + } + } + if (!config.name) { + throw new Error(`Container name must be a string, but got boolean true`); + } + return { + name: config.name, + Image: config.image, + Cmd: Array.isArray(config.command) + ? config.command + : typeof config.command === 'string' + ? [config.command] + : undefined, + Entrypoint: config.entrypoint, + Env: config.environment + ? Object.keys(config.environment).map(key => `${key}=${config.environment[key]}`) + : undefined, + // WorkingDir: config.workingDir, + // { '/data': {} } + Volumes: config.volumes?.reduce((acc, vol) => (acc[vol] = {}), {}), + Labels: config.labels, + ExposedPorts: config.ports + ? config.ports.reduce((acc, port) => { + acc[`${port.containerPort}/${port.protocol || 'tcp'}`] = {}; + return acc; + }, {}) + : undefined, + HostConfig: { + // Binds: config.binds, + PortBindings: config.ports + ? config.ports.reduce((acc, port) => { + acc[`${port.containerPort}/${port.protocol || 'tcp'}`] = [ + { + HostPort: port.hostPort ? port.hostPort.toString() : undefined, + HostIp: port.hostIP || undefined, + }, + ]; + return acc; + }, {}) + : undefined, + Mounts: mounts, + NetworkMode: config.networkMode === true ? '' : config.networkMode || undefined, + // Links: config.links, + // Dns: config.dns, + // DnsOptions: config.dnsOptions, + // DnsSearch: config.dnsSearch, + ExtraHosts: config.extraHosts, + // VolumesFrom: config.volumesFrom, + Privileged: config.security?.privileged, + CapAdd: config.security?.capAdd, + CapDrop: config.security?.capDrop, + UsernsMode: config.security?.usernsMode, + IpcMode: config.security?.ipc, + PidMode: config.security?.pid, + GroupAdd: config.security?.groupAdd?.map(g => g.toString()), + ReadonlyRootfs: config.readOnly, + RestartPolicy: { + Name: config.restart?.policy || 'no', + MaximumRetryCount: config.restart?.maxRetries || 0, + }, + CpuShares: config.resources?.cpuShares, + CpuPeriod: config.resources?.cpuPeriod, + CpuQuota: config.resources?.cpuQuota, + CpusetCpus: config.resources?.cpus?.toString(), + Memory: config.resources?.memory, + MemorySwap: config.resources?.memorySwap, + MemoryReservation: config.resources?.memoryReservation, + // OomKillDisable: config.resources?.oomKillDisable, + // OomScoreAdj: config.resources?.oomScoreAdj, + LogConfig: config.logging + ? { + Type: config.logging.driver || 'json-file', + Config: config.logging.options || {}, + } + : undefined, + SecurityOpt: [ + ...(config.security?.seccomp ? [`seccomp=${config.security.seccomp}`] : []), + ...(config.security?.apparmor ? [`apparmor=${config.security.apparmor}`] : []), + ...(config.security?.noNewPrivileges ? ['no-new-privileges:true'] : []), + ], + Sysctls: config.sysctls, + Init: config.init, + }, + StopSignal: config.stop?.signal, + StopTimeout: config.stop?.gracePeriodSec, + Tty: config.tty, + OpenStdin: config.openStdin, + }; + } + /** + * Create and start a container with the given configuration. No checks are done. + */ + async containerRun(config) { + if (this.#dockerode) { + const container = await this.#dockerode.createContainer(_a.getDockerodeConfig(config)); + await container.start(); + return { stdout: `Container ${config.name} started`, stderr: '' }; + } + try { + return await this.#exec(`run ${_a.toDockerRun(config)}`); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** + * Create a container with the given configuration without starting it. No checks are done. + */ + async containerCreate(config) { + if (this.#dockerode) { + const container = await this.#dockerode.createContainer(_a.getDockerodeConfig(config)); + return { stdout: `Container ${container.id} created`, stderr: '' }; + } + try { + return await this.#exec(`create ${_a.toDockerRun(config, true)}`); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** + * Recreate a container + * + * This function checks if a container is running, stops it if necessary, + * removes it and creates a new one with the given configuration. + * The container is not started after creation. + * + * @param config new configuration + * @returns stdout and stderr of the create command + */ + async containerReCreate(config) { + if (this.#dockerode) { + // Get if the container is running + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === config.name); + if (containerInfo) { + const container = this.#dockerode.getContainer(containerInfo.id); + if (containerInfo.status === 'running' || containerInfo.status === 'restarting') { + await container.stop(); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) { + this.adapter.log.warn(`Cannot remove container: still running`); + throw new Error(`Container ${containerInfo.id} still running after stop`); + } + } + // Remove container + await container.remove(); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id)) { + this.adapter.log.warn(`Cannot remove container: still existing`); + throw new Error(`Container ${containerInfo.id} still found after remove`); + } + } + const newContainer = await this.#dockerode.createContainer(_a.getDockerodeConfig(config)); + return { stdout: `Container ${newContainer.id} created`, stderr: '' }; + } + try { + // Get if the container is running + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === config.name); + if (containerInfo) { + if (containerInfo.status === 'running' || containerInfo.status === 'restarting') { + const stopResult = await this.#exec(`stop ${containerInfo.id}`); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) { + this.adapter.log.warn(`Cannot remove container: ${stopResult.stderr || stopResult.stdout}`); + throw new Error(`Container ${containerInfo.id} still running after stop`); + } + } + // Remove container + const rmResult = await this.#exec(`rm ${containerInfo.id}`); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id)) { + this.adapter.log.warn(`Cannot remove container: ${rmResult.stderr || rmResult.stdout}`); + throw new Error(`Container ${containerInfo.id} still found after remove`); + } + } + return await this.#exec(`create ${_a.toDockerRun(config, true)}`); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + async containerCreateCompose(compose) { + try { + return await this.#exec(`compose -f ${compose} create`); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** List all images */ + async imageList() { + if (this.#dockerode) { + const images = await this.#dockerode.listImages(); + return images.map(img => { + const repoTag = img.RepoTags && img.RepoTags.length ? img.RepoTags[0] : ':'; + const [repository, tag] = repoTag.split(':'); + return { + repository, + tag, + id: img.Id.startsWith('sha256:') ? img.Id.substring(7, 19) : img.Id.substring(0, 12), + createdSince: new Date(img.Created * 1000).toISOString(), + size: img.Size, + }; + }); + } + try { + const { stdout } = await this.#exec('images --format "{{.Repository}}:{{.Tag}};{{.ID}};{{.CreatedAt}};{{.Size}}"'); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [repositoryTag, id, createdSince, size] = line.split(';'); + const [repository, tag] = repositoryTag.split(':'); + return { + repository, + tag, + id, + createdSince, + size: this.#parseSize(size), + }; + }); + } + catch (e) { + this.adapter.log.debug(`Cannot list images: ${e.message}`); + return []; + } + } + /** Build an image from a Dockerfile */ + async imageBuild(dockerfilePath, tag) { + if (this.#dockerode) { + try { + const stream = await this.#dockerode.buildImage(dockerfilePath, { + t: tag, + dockerfile: dockerfilePath, + }); + return new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + const onFinished = (err) => { + if (err) { + return reject(err); + } + resolve({ stdout, stderr }); + }; + const onProgress = (event) => { + if (event.stream) { + stdout += event.stream; + } + if (event.error) { + stderr += event.error; + } + this.adapter.log.debug(JSON.stringify(event)); + }; + this.#dockerode.modem.followProgress(stream, onFinished, onProgress); + }); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + try { + return await this.#exec(`build -t ${tag} -f ${dockerfilePath} .`); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** Tag an image with a new tag */ + async imageTag(imageId, newTag) { + try { + return await this.#exec(`tag ${imageId} ${newTag}`); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** Remove an image */ + async imageRemove(imageId) { + try { + const result = await this.#exec(`rmi ${imageId}`); + const images = await this.imageList(); + if (images.find(it => `${it.repository}:${it.tag}` === imageId)) { + return { stdout: '', stderr: `Image ${imageId} still found after deletion`, images }; + } + return { ...result, images }; + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + static rodeInspect2DockerImageInspect(data) { + return { + Id: data.Id.startsWith('sha256:') ? data.Id.substring(7, 19) : data.Id.substring(0, 12), + RepoTags: data.RepoTags, + RepoDigests: data.RepoDigests, + Parent: data.Parent, + Comment: data.Comment, + Created: data.Created, + DockerVersion: data.DockerVersion, + Author: data.Author, + Architecture: data.Architecture, + Os: data.Os, + Size: data.Size, + GraphDriver: { + Data: data.GraphDriver.Data, + Name: data.GraphDriver.Name, + }, + RootFS: data.RootFS, + Config: { + ...data.Config, + Entrypoint: Array.isArray(data.Config.Entrypoint) + ? data.Config.Entrypoint + : typeof data.Config.Entrypoint === 'string' + ? [data.Config.Entrypoint] + : [], + }, + }; + } + /** Inspect an image */ + async imageInspect(imageId) { + if (this.#dockerode) { + const image = this.#dockerode.getImage(imageId); + const data = await image.inspect(); + return _a.rodeInspect2DockerImageInspect(data); + } + try { + const { stdout } = await this.#exec(`inspect ${imageId}`); + return JSON.parse(stdout)[0]; + } + catch (e) { + this.adapter.log.debug(`Cannot inspect image: ${e.message.toString()}`); + return null; + } + } + async imagePrune() { + if (this.#dockerode) { + try { + await this.#dockerode.pruneImages(); + return { stdout: 'Unused images pruned', stderr: '' }; + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + try { + return await this.#exec(`image prune -f`); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + #parseSize(sizeStr) { + const units = { + B: 1, + KB: 1024, + MB: 1024 * 1024, + GB: 1024 * 1024 * 1024, + TB: 1024 * 1024 * 1024 * 1024, + }; + const match = sizeStr.match(/^([\d.]+)([KMGTP]?B)$/); + if (match) { + const value = parseFloat(match[1]); + const unit = match[2]; + return value * (units[unit] || 1); + } + return 0; + } + /** + * Stop a container + * + * @param container Container name or ID + */ + async containerStop(container) { + if (this.#dockerode) { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + const dockerContainer = this.#dockerode.getContainer(containerInfo.id); + await dockerContainer.stop(); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) { + throw new Error(`Container ${container} still running after stop`); + } + return { stdout: `Contained ${containerInfo.id} stopped`, stderr: '', containers }; + } + try { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + const result = await this.#exec(`stop ${containerInfo.id}`); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) { + throw new Error(`Container ${container} still running after stop`); + } + return { ...result, containers }; + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** + * Start a container + * + * @param container Container name or ID + */ + async containerStart(container) { + if (this.#dockerode) { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + const dockerContainer = this.#dockerode.getContainer(containerInfo.id); + await dockerContainer.start(); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id && it.status !== 'running' && it.status !== 'restarting')) { + throw new Error(`Container ${container} still running after stop`); + } + return { stdout: `Container ${containerInfo.id} started`, stderr: '', containers }; + } + try { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + const result = await this.#exec(`start ${containerInfo.id}`); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id && it.status !== 'running' && it.status !== 'restarting')) { + throw new Error(`Container ${container} still running after stop`); + } + return { ...result, containers }; + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** + * Restart a container + * + * This function restarts a container by its name or ID. + * It accepts an optional timeout in seconds to wait before killing the container (default is 5 seconds). + * + * @param container Container name or ID + * @param timeoutSeconds Timeout in seconds to wait before killing the container (default: 5) + */ + async containerRestart(container, timeoutSeconds) { + container ||= this.getDefaultContainerName(); + if (this.#dockerode) { + const containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + const dockerContainer = this.#dockerode.getContainer(containerInfo.id); + await dockerContainer.restart({ t: timeoutSeconds || 5 }); + return { stdout: `Container ${containerInfo.id} restarted`, stderr: '' }; + } + try { + const containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + return await this.#exec(`restart -t ${timeoutSeconds || 5} ${containerInfo.id}`); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** Find the IP address of a container, via which it can be reached from the host */ + async getIpOfContainer(containerName) { + containerName ||= this.getDefaultContainerName(); + const data = await this.containerInspect(containerName); + if (!data?.NetworkSettings?.Networks) { + throw new Error(`No network settings found for container ${containerName}`); + } + for (const n in data.NetworkSettings.Networks) { + if (data.NetworkSettings.Networks[n].IPAddress) { + return data.NetworkSettings.Networks[n].IPAddress; + } + } + throw new Error(`No IP address found for container ${containerName}`); + } + /** + * Remove the container and if necessary, stop it first + * + * @param container Container name or ID + */ + async containerRemove(container) { + try { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + // ensure that container is stopped + if (containerInfo.status === 'running' || containerInfo.status === 'restarting') { + // stop container + const result = await this.#exec(`stop ${containerInfo.id}`); + if (result.stderr) { + throw new Error(`Cannot stop container ${container}: ${result.stderr}`); + } + } + const result = await this.#exec(`rm ${container}`); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id)) { + throw new Error(`Container ${container} still found after stop`); + } + return { ...result, containers }; + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** + * List all containers + * + * @param all If true, list all containers. If false, list only running containers. Default is true. + */ + async containerList(all = true) { + if (this.#dockerode) { + const containers = await this.#dockerode.listContainers({ all }); + return containers.map(cont => { + const statusKey = cont.State.toLowerCase(); + let status; + if (statusKey === 'up') { + status = 'running'; + } + else if (statusKey === 'exited') { + status = 'exited'; + } + else if (statusKey === 'created') { + status = 'created'; + } + else if (statusKey === 'paused') { + status = 'paused'; + } + else if (statusKey === 'restarting') { + status = 'restarting'; + } + else { + status = statusKey; + } + // Try to convert: Up 6 minutes, Up 6 hours, Up 6 days to minutes + const uptimeMatch = cont.Status.match(/Up (\d+) (seconds?|minutes?|hours?|days?)/); + let minutes = 0; + if (uptimeMatch) { + const value = parseInt(uptimeMatch[1], 10); + const unit = uptimeMatch[2]; + if (unit.startsWith('hour')) { + minutes = value * 60; + } + else if (unit.startsWith('day')) { + minutes = value * 60 * 24; + } + else if (unit.startsWith('second')) { + minutes = Math.ceil(value / 60); + } + } + else if (cont.Status === 'Up About a minute') { + minutes = 1; + } + return { + id: cont.Id.substring(0, 12), + image: cont.Image, + command: cont.Command, + createdAt: new Date(cont.Created * 1000).toISOString(), + status, + uptime: minutes.toString(), + ports: cont.Ports.map(p => `${p.IP ? `${p.IP}:` : ''}${p.PublicPort ? `${p.PublicPort}->` : ''}${p.PrivatePort}/${p.Type}`).join(', '), + names: cont.Names.map(n => (n.startsWith('/') ? n.substring(1) : n)).join(', '), + labels: cont.Labels || {}, + }; + }); + } + try { + const { stdout } = await this.#exec(`ps ${all ? '-a' : ''} --format "{{.Names}};{{.Status}};{{.ID}};{{.Image}};{{.Command}};{{.CreatedAt}};{{.Ports}};{{.Labels}}"`); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [names, statusInfo, id, image, command, createdAt, ports, labels] = line.split(';'); + const [status, ...uptime] = statusInfo.split(' '); + let statusKey = status.toLowerCase(); + if (statusKey === 'up') { + statusKey = 'running'; + } + return { + id, + image, + command, + createdAt, + status: statusKey, + uptime: uptime.join(' '), + ports, + names, + labels: labels?.split(',').reduce((acc, label) => { + const [key, value] = label.split('='); + if (key && value) { + acc[key] = value; + } + return acc; + }, {}) || {}, + }; + }); + } + catch (e) { + this.adapter.log.debug(`Cannot list containers: ${e.message}`); + return []; + } + } + /** + * Get the logs of a container + * + * @param containerNameOrId Container name or ID + * @param options Options for logs + * @param options.tail Number of lines to show from the end of the logs + * @param options.follow If true, follow the logs (not implemented yet) + */ + async containerLogs(containerNameOrId, options = {}) { + if (this.#dockerode) { + try { + const container = this.#dockerode.getContainer(containerNameOrId); + const data = await container.logs({ + stdout: true, + stderr: true, + follow: false, + tail: options.tail || undefined, + }); + return data + .toString() + .split('\n') + .filter(line => line.trim() !== ''); + } + catch (e) { + return e + .toString() + .split('\n') + .map((line) => line.trim()); + } + } + try { + const args = []; + if (options.tail !== undefined) { + args.push(`--tail ${options.tail}`); + } + if (options.follow) { + args.push(`--follow`); + throw new Error('Follow option is not implemented yet'); + } + const result = await this.#exec(`logs${args.length ? ` ${args.join(' ')}` : ''} ${containerNameOrId}`); + return (result.stdout || result.stderr).split('\n').filter(line => line.trim() !== ''); + } + catch (e) { + return e + .toString() + .split('\n') + .map((line) => line.trim()); + } + } + static dockerodeInspect2DockerContainerInspect(data) { + return { + ...data, + }; + } + /** Inspect a container */ + async containerInspect(containerNameOrId) { + if (this.#dockerode) { + try { + const container = this.#dockerode.getContainer(containerNameOrId); + const dResult = await container.inspect(); + const result = _a.dockerodeInspect2DockerContainerInspect(dResult); + if (result.State.Running) { + result.Stats = (await this.containerGetRamAndCpuUsage(containerNameOrId)) || undefined; + } + return result; + } + catch (e) { + this.adapter.log.debug(`Cannot inspect container: ${e.message.toString()}`); + return null; + } + } + try { + const { stdout } = await this.#exec(`inspect ${containerNameOrId}`); + const result = JSON.parse(stdout)[0]; + if (result.State.Running) { + result.Stats = (await this.containerGetRamAndCpuUsage(containerNameOrId)) || undefined; + } + return result; + } + catch (e) { + this.adapter.log.debug(`Cannot inspect container: ${e.message.toString()}`); + return null; + } + } + async containerPrune() { + if (this.#dockerode) { + try { + const result = await this.#dockerode.pruneContainers(); + return { stdout: `Containers pruned: ${result.ContainersDeleted?.join(', ') || 'none'}`, stderr: '' }; + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + try { + return await this.#exec(`container prune -f`); + } + catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + /** + * Build a docker run command string from ContainerConfig + */ + static toDockerRun(config, create) { + const args = []; + // detach / interactive + if (config.detach !== false && !create) { + // default is true + args.push('-d'); + } + if (config.tty) { + args.push('-t'); + } + if (config.stdinOpen) { + args.push('-i'); + } + if (config.removeOnExit) { + args.push('--rm'); + } + // name + if (config.name) { + args.push('--name', config.name); + } + // hostname / domain + if (config.hostname) { + args.push('--hostname', config.hostname); + } + if (config.domainname) { + args.push('--domainname', config.domainname); + } + // environment + if (config.environment) { + for (const [key, value] of Object.entries(config.environment)) { + if (key && value) { + args.push('-e', `${key}=${value}`); + } + } + } + if (config.envFile) { + for (const file of config.envFile) { + args.push('--env-file', file); + } + } + // labels + if (config.labels) { + for (const [key, value] of Object.entries(config.labels)) { + args.push('--label', `${key}=${value}`); + } + } + // ports + if (config.publishAllPorts) { + args.push('-P'); + } + if (config.ports) { + for (const p of config.ports) { + if (!p.containerPort) { + continue; + } + const mapping = (p.hostIP ? `${p.hostIP}:` : '') + + (p.hostPort ? `${p.hostPort}:` : '') + + p.containerPort + + (p.protocol ? `/${p.protocol}` : ''); + args.push('-p', mapping); + } + } + // volumes / mounts + if (config.volumes) { + for (const v of config.volumes) { + args.push('-v', v); + } + } + if (config.mounts) { + for (const m of config.mounts) { + let mount = `type=${m.type},target=${m.target}`; + if (m.source) { + mount += `,source=${m.source}`; + } + if (m.readOnly) { + mount += `,readonly`; + } + args.push('--mount', mount); + } + } + // restart policy + if (config.restart?.policy) { + const val = config.restart.policy === 'on-failure' && config.restart.maxRetries + ? `on-failure:${config.restart.maxRetries}` + : config.restart.policy; + args.push('--restart', val); + } + // user & workdir + if (config.user) { + args.push('--user', String(config.user)); + } + if (config.workdir) { + args.push('--workdir', config.workdir); + } + // logging + if (config.logging?.driver) { + args.push('--log-driver', config.logging.driver); + if (config.logging.options) { + for (const [k, v] of Object.entries(config.logging.options)) { + args.push('--log-opt', `${k}=${v}`); + } + } + } + // security + if (config.security?.privileged) { + args.push('--privileged'); + } + if (config.security?.capAdd) { + for (const cap of config.security.capAdd) { + args.push('--cap-add', cap); + } + } + if (config.security?.capDrop) { + for (const cap of config.security.capDrop) { + args.push('--cap-drop', cap); + } + } + if (config.security?.noNewPrivileges) { + args.push('--security-opt', 'no-new-privileges'); + } + if (config.security?.apparmor) { + args.push('--security-opt', `apparmor=${config.security.apparmor}`); + } + // network + if (config.networkMode && typeof config.networkMode === 'string') { + args.push('--network', config.networkMode); + } + // extra hosts + if (config.extraHosts) { + for (const host of config.extraHosts) { + if (typeof host === 'string') { + args.push('--add-host', host); + } + else { + args.push('--add-host', `${host.host}:${host.ip}`); + } + } + } + // sysctls + if (config.sysctls) { + for (const [k, v] of Object.entries(config.sysctls)) { + args.push('--sysctl', `${k}=${v}`); + } + } + // stop signal / timeout + if (config.stop?.signal) { + args.push('--stop-signal', config.stop.signal); + } + if (config.stop?.gracePeriodSec !== undefined) { + args.push('--stop-timeout', String(config.stop.gracePeriodSec)); + } + // resources + if (config.resources?.cpus) { + args.push('--cpus', String(config.resources.cpus)); + } + if (config.resources?.memory) { + args.push('--memory', String(config.resources.memory)); + } + // image + if (!config.image) { + throw new Error('ContainerConfig.image is required for docker run'); + } + args.push(config.image); + // command override + if (config.command) { + if (Array.isArray(config.command)) { + args.push(...config.command); + } + else { + args.push(config.command); + } + } + return args.join(' '); + } + async networkList() { + if (this.#dockerode) { + const networks = await this.#dockerode.listNetworks(); + return networks.map(net => ({ + name: net.Name, + id: net.Id, + driver: net.Driver, + scope: net.Scope, + })); + } + // docker network ls + try { + const { stdout } = await this.#exec(`network ls --format "{{.Name}};{{.ID}};{{.Driver}};{{.Scope}}"`); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [name, id, driver, scope] = line.split(';'); + return { name, id, driver: driver, scope }; + }); + } + catch (e) { + this.adapter.log.debug(`Cannot list networks: ${e.message.toString()}`); + return []; + } + } + async networkCreate(name, driver) { + if (this.#dockerode) { + const net = await this.#dockerode.createNetwork({ Name: name, Driver: driver || 'bridge' }); + const networks = await this.networkList(); + if (!networks.find(it => it.name === name)) { + throw new Error(`Network ${name} not found after creation`); + } + return { stdout: `Network ${net.id} created`, stderr: '', networks }; + } + const result = await this.#exec(`network create ${driver ? `--driver ${driver}` : ''} ${name}`); + const networks = await this.networkList(); + if (!networks.find(it => it.name === name)) { + throw new Error(`Network ${name} not found after creation`); + } + return { ...result, networks }; + } + async networkRemove(networkId) { + const result = await this.#exec(`network remove ${networkId}`); + const networks = await this.networkList(); + if (networks.find(it => it.id === networkId)) { + throw new Error(`Network ${networkId} still found after deletion`); + } + return { ...result, networks }; + } + async networkPrune() { + if (this.#dockerode) { + const result = await this.#dockerode.pruneNetworks(); + const networks = await this.networkList(); + return { stdout: `Networks pruned`, stderr: JSON.stringify(result), networks }; + } + const result = await this.#exec(`network prune -f`); + const networks = await this.networkList(); + return { ...result, networks }; + } + /** List all volumes */ + async volumeList() { + if (this.#dockerode) { + const volumesData = await this.#dockerode.listVolumes(); + return (volumesData.Volumes || []).map(vol => ({ + name: vol.Name, + driver: vol.Driver, + volume: vol.Mountpoint, + })); + } + // docker network ls + try { + const { stdout } = await this.#exec(`volume ls --format "{{.Name}};{{.Driver}};{{.Mountpoint}}"`); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [name, driver, volume] = line.split(';'); + return { name, driver: driver, volume }; + }); + } + catch (e) { + this.adapter.log.debug(`Cannot list networks: ${e.message.toString()}`); + return []; + } + } + async volumeCopyTo(volumeName, sourcePath) { + const tempContainerName = `iobroker_temp_copy_${Date.now()}`; + if (this.#dockerode) { + // Check if alpine image is there + const images = await this.imageList(); + if (!images.find(img => img.repository === 'alpine')) { + const pullResult = await this.imagePull('alpine'); + if (pullResult.stderr) { + return { stdout: '', stderr: `Cannot pull alpine image: ${pullResult.stderr}` }; + } + } + // create a temporary container with volume mounted + const container = await this.#dockerode.createContainer({ + Image: 'alpine', + name: tempContainerName, + Cmd: ['sleep', '30'], + HostConfig: { + Binds: [`${volumeName}:/data`], + }, + }); + try { + await container.start(); + // Lazy loading tar-fs + if (!this.#tarPack) { + await import('tar-fs') + .then(tarFs => (this.#tarPack = tarFs.default.pack)) + .catch(e => this.adapter.log.error(`Cannot import tar-fs package: ${e.message}`)); + } + if (!this.#tarPack) { + throw new Error('Cannot load tar-fs package'); + } + // use dockerode to copy files + const pack = this.#tarPack(sourcePath); + await container.putArchive(pack, { path: '/data' }); + return { stdout: 'Data copied to volume', stderr: '' }; + } + catch (e) { + return { stdout: '', stderr: `Cannot copy data to volume: ${e.message}` }; + } + finally { + // remove temporary container + try { + await container.stop(); + await container.remove({ force: true }); + } + catch (e) { + this.adapter.log.warn(`Cannot remove temporary container ${tempContainerName}: ${e.message}`); + } + } + } + // create a temporary container with volume mounted + const createResult = await this.#exec(`create -v ${volumeName}:/data --name ${tempContainerName} alpine sleep 60`); + if (createResult.stderr) { + return { stdout: '', stderr: `Cannot create temporary container: ${createResult.stderr}` }; + } + try { + const copyResult = await this.#exec(`cp ${sourcePath} ${tempContainerName}:/data/`); + if (copyResult.stderr) { + return { stdout: '', stderr: `Cannot copy data to volume: ${copyResult.stderr}` }; + } + return { stdout: 'Data copied to volume', stderr: '' }; + } + finally { + // remove temporary container + await this.#exec(`rm -f ${tempContainerName}`); + } + } + /** + * Create a volume + * + * @param name Volume name + * @param driver Volume driver + * @param volume Volume options (depends on driver) + */ + async volumeCreate(name, driver, volume) { + if (this.#dockerode) { + const vol = await this.#dockerode.createVolume({ + Name: name, + Driver: driver || 'local', + DriverOpts: volume ? { device: volume, o: 'bind', type: 'none' } : {}, + }); + const volumes = await this.volumeList(); + if (!volumes.find(it => it.name === name)) { + throw new Error(`Network ${name} not found after creation`); + } + return { stdout: `Volume ${vol.Name} created`, stderr: '', volumes }; + } + let result; + if (driver === 'local' || !driver) { + if (volume) { + result = await this.#exec(`volume create local --opt type=none --opt device=${volume} --opt o=bind ${name}`); + } + else { + result = await this.#exec(`volume create ${name}`); + } + } + else { + throw new Error('not implemented'); + } + const volumes = await this.volumeList(); + if (!volumes.find(it => it.name === name)) { + throw new Error(`Network ${name} not found after creation`); + } + return { ...result, volumes }; + } + /** Remove a volume */ + async volumeRemove(volumeName) { + const result = await this.#exec(`volume remove ${volumeName}`); + const volumes = await this.volumeList(); + if (volumes.find(it => it.name === volumeName)) { + throw new Error(`Volume ${volumeName} still found after deletion`); + } + return { ...result, volumes }; + } + /** Prune unused volumes */ + async volumePrune() { + if (this.#dockerode) { + const result = await this.#dockerode.pruneVolumes(); + const volumes = await this.volumeList(); + return { stdout: `Volumes pruned`, stderr: JSON.stringify(result), volumes }; + } + const result = await this.#exec(`volume prune -f`); + const volumes = await this.volumeList(); + return { ...result, volumes }; + } + /** Stop own containers if necessary */ + async destroy() { + if (this.#monitoringInterval) { + clearInterval(this.#monitoringInterval); + this.#monitoringInterval = null; + } + for (const container of this.#ownContainers) { + if (container.iobEnabled !== false && container.iobStopOnUnload && container.name) { + this.adapter.log.info(`Stopping own container ${container.name} on destroy`); + try { + await this.containerStop(container.name); + } + catch (e) { + this.adapter.log.warn(`Cannot stop own container ${container.name} on destroy: ${e.message}`); + } + } + } + } +} +_a = DockerManager; +exports.default = DockerManager; +//# sourceMappingURL=DockerManager.js.map \ No newline at end of file diff --git a/build/lib/DockerManager.js.map b/build/lib/DockerManager.js.map new file mode 100644 index 00000000..8eee7c49 --- /dev/null +++ b/build/lib/DockerManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DockerManager.js","sourceRoot":"","sources":["../../src/lib/DockerManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/lib/aggregate.js b/build/lib/aggregate.js new file mode 100644 index 00000000..bda4b2e4 --- /dev/null +++ b/build/lib/aggregate.js @@ -0,0 +1,1481 @@ +"use strict"; +// THIS file should be identical with SQL and history adapter's one +Object.defineProperty(exports, "__esModule", { value: true }); +exports.calcDiff = calcDiff; +exports.initAggregate = initAggregate; +exports.aggregation = aggregation; +exports.finishAggregationForIntegralEx = finishAggregationForIntegralEx; +exports.finishAggregationForIntegral = finishAggregationForIntegral; +exports.finishAggregationPercentile = finishAggregationPercentile; +exports.finishAggregation = finishAggregation; +exports.beautify = beautify; +exports.sendResponse = sendResponse; +exports.sendResponseCounter = sendResponseCounter; +exports.sortByTs = sortByTs; +/** + * Calculate the square of triangle between two data points on chart + * val | | + * | | /-----|----- + * | | /__--/######| + * |----|--/############| + * | |###############| + * +----o---------------n--->time + * Square is the #, deltaT = x2 - x1, DeltaY = y2 - y1 + */ +function calcDiff(oldVal, newVal) { + // (Xnew - Xold) / 3600000 (to hours) + const deltaT = (newVal.ts - oldVal.ts) / 3_600_000; // ms => hours + // if deltaT is negative, we have a problem as the time cannot go back + if (deltaT < 0) { + return { square: 0, deltaT: 0 }; + } + const square = ((newVal.val || 0) + (oldVal.val || 0)) * (deltaT * 0.5); + return { square, deltaT }; +} +function interpolate2points(p1, p2, ts) { + const dx = p2.ts - p1.ts; + // threat null as zero + const dy = (p2.val || 0) - (p1.val || 0); + if (!dx) { + return p1.val; + } + return (dy * (ts - p1.ts)) / dx + p1.val; +} +function initAggregate(initialOptions, id, timeIntervals, log) { + const options = initialOptions; + options.log = log; + options.id = id; // id is needed for because of addId option + if (!options.log) { + options.log = () => { }; + // To save the complex outputs + options.logDebug = false; + } + // step; // 1 Step is 1 second + if (options.step === null || options.step === undefined || options.step <= 0) { + options.step = (options.end - options.start) / options.count; + } + // Limit 2000 + if ((options.end - options.start) / options.step > options.limit) { + options.step = (options.end - options.start) / options.limit; + } + if (timeIntervals) { + options.timeIntervals = timeIntervals; + options.maxIndex = options.timeIntervals.length - 1; + } + else { + // MaxIndex is the index, that can really called without -1 + options.maxIndex = Math.ceil((options.end - options.start) / options.step - 1); + } + options.processing = []; + options.result = []; // finalResult + options.averageCount = []; + options.quantileDataPoints = []; + options.integralDataPoints = []; + options.totalIntegralDataPoints = []; + options.aggregate = options.aggregate || 'minmax'; + options.overallLength = 0; + options.currentTimeInterval = 0; + if (options.aggregate === 'percentile') { + if (typeof options.percentile !== 'number' || options.percentile < 0 || options.percentile > 100) { + options.percentile = 50; + } + options.quantile = options.percentile / 100; // Internally we use quantile for percentile too + } + if (options.aggregate === 'quantile') { + if (typeof options.quantile !== 'number' || options.quantile < 0 || options.quantile > 1) { + options.quantile = 0.5; + } + } + if (options.aggregate === 'integral') { + if (typeof options.integralUnit !== 'number' || options.integralUnit <= 0) { + options.integralUnit = 60; + } + options.integralUnit *= 1000; // Convert to milliseconds + } + if (options.logDebug && options.log) { + options.log(`Initialize: maxIndex = ${options.maxIndex}, step = ${!timeIntervals ? options.step : 'smart'}, start = ${options.start}, end = ${options.end}`); + } + // pre-fill the result with timestamps (add one before start and one after end) + try { + options.processing.length = options.maxIndex + 2; + } + catch (err) { + err.message += `: ${options.maxIndex + 2}`; + throw err; + } + // We define the array length, but do not prefill values, we do that on runtime when needed + options.processing[0] = { + val: { ts: null, val: null }, + max: { ts: null, val: null }, + min: { ts: null, val: null }, + start: { ts: null, val: null }, + end: { ts: null, val: null }, + }; + options.processing[options.maxIndex + 2] = { + val: { ts: null, val: null }, + max: { ts: null, val: null }, + min: { ts: null, val: null }, + start: { ts: null, val: null }, + end: { ts: null, val: null }, + }; + if (options.aggregate === 'average') { + options.averageCount[0] = 0; + options.averageCount[options.maxIndex + 2] = 0; + } + if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { + options.quantileDataPoints[0] = []; + options.quantileDataPoints[options.maxIndex + 2] = []; + } + if (options.aggregate === 'integral') { + options.integralDataPoints[0] = []; + options.integralDataPoints[options.maxIndex + 2] = []; + } + return options; +} +function aggregation(options, data) { + let index; + let preIndex; + let collectedTooEarlyData = []; + let collectedTooLateData = []; + let preIndexValueFound = false; + let postIndexValueFound = false; + for (let i = 0; i < data.length; i++) { + if (!data[i]) { + continue; + } + if (typeof data[i].ts !== 'number') { + data[i].ts = parseInt(data[i].ts, 10); + } + if (options.timeIntervals) { + // We have specific time intervals and collects all data according to this information + const dataTs = data[i].ts; + // Find a time interval for this timestamp + if (dataTs < options.start) { + index = 0; + } + else if (dataTs >= options.end) { + index = options.timeIntervals.length + 1; + } + else if (dataTs >= options.timeIntervals[options.currentTimeInterval].end) { + // Look for the next interval + options.currentTimeInterval++; + while (options.currentTimeInterval < options.timeIntervals.length) { + if (options.timeIntervals[options.currentTimeInterval].start <= dataTs && + dataTs < options.timeIntervals[options.currentTimeInterval].end) { + break; + } + options.currentTimeInterval++; + } + index = options.currentTimeInterval + 1; + } + else { + index = options.currentTimeInterval + 1; + } + } + else { + // Our intervals are equidistant and we can calculate them + preIndex = Math.floor((data[i].ts - options.start) / options.step); + // store all border values + if (preIndex < 0) { + index = 0; + // if the ts is even earlier than the "pre-interval" ignore it, else we collect all data there + if (preIndex < -1) { + collectedTooEarlyData.push(data[i]); + continue; + } + preIndexValueFound = true; + } + else if (preIndex > options.maxIndex) { + index = options.maxIndex + 2; + // if the ts is even later than the "post-interval" ignore it, else we collect all data there + if (preIndex > options.maxIndex + 1) { + collectedTooLateData.push(data[i]); + continue; + } + postIndexValueFound = true; + } + else { + index = preIndex + 1; + } + options.overallLength++; + } + // Init data for time slot + if (options.processing[index] === undefined) { + // lazy initialization of data structure + options.processing[index] = { + val: { ts: null, val: null }, + max: { ts: null, val: null }, + min: { ts: null, val: null }, + start: { ts: null, val: null }, + end: { ts: null, val: null }, + }; + if (options.aggregate === 'average' || options.aggregate === 'count') { + options.averageCount[index] = 0; + } + if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { + options.quantileDataPoints[index] = []; + } + if (options.aggregate === 'integral') { + options.integralDataPoints[index] = []; + } + } + aggregationLogic(data[i], index, options); + } + // If no data was found in the pre-interval, but we have earlier data, we put the latest of them in the pre-interval + if (!preIndexValueFound && collectedTooEarlyData.length > 0) { + collectedTooEarlyData = collectedTooEarlyData.sort(sortByTs); + options.overallLength++; + aggregationLogic(collectedTooEarlyData[collectedTooEarlyData.length - 1], 0, options); + } + // If no data was found in the post-interval, but we have later data, we put the earliest of them in the post-interval + if (!postIndexValueFound && collectedTooLateData.length > 0) { + collectedTooLateData = collectedTooLateData.sort(sortByTs); + options.overallLength++; + aggregationLogic(collectedTooLateData[0], options.maxIndex + 2, options); + } + return { processing: options.processing, step: options.step, sourceLength: data.length }; +} +/** Execute logic for every entry in the initial series array */ +function aggregationLogic(data, index, options) { + if (!options.processing || !options.processing[index]) { + if (options.logDebug && options.log) { + options.log(`Data index ${index} not initialized, ignore!`); + } + return; + } + if (options.aggregate !== 'minmax' && !options.processing[index].val.ts) { + if (options.timeIntervals) { + if (index === 0) { + // If it is pre interval, make an estimation + options.processing[0].val.ts = + options.timeIntervals[0].start - + Math.round((options.timeIntervals[0].end - options.timeIntervals[0].start) / 2); + } + else if (index > options.timeIntervals.length) { + // If it is post interval, make an estimation + options.processing[index].val.ts = + options.timeIntervals[options.timeIntervals.length - 1].end + + Math.round((options.timeIntervals[options.timeIntervals.length - 1].end - + options.timeIntervals[options.timeIntervals.length - 1].start) / + 2); + } + else { + // Get the middle of the interval + options.processing[index].val.ts = + options.timeIntervals[index - 1].start + + Math.round((options.timeIntervals[index - 1].end - options.timeIntervals[index - 1].start) / 2); + } + } + else { + options.processing[index].val.ts = Math.round(options.start + (index - 1 + 0.5) * options.step); + } + } + if (options.aggregate === 'max') { + if (options.processing[index].val.val === null || options.processing[index].val.val < data.val) { + options.processing[index].val.val = data.val; + } + } + else if (options.aggregate === 'min') { + if (options.processing[index].val.val === null || options.processing[index].val.val > data.val) { + options.processing[index].val.val = data.val; + } + } + else if (options.aggregate === 'average') { + options.processing[index].val.val += parseFloat(data.val); + options.averageCount[index]++; + } + else if (options.aggregate === 'count') { + options.averageCount[index]++; + } + else if (options.aggregate === 'total') { + options.processing[index].val.val += parseFloat(data.val); + } + else if (options.aggregate === 'minmax') { + if (options.processing[index].min.ts === null) { + options.processing[index].min.ts = data.ts; + options.processing[index].min.val = data.val; + options.processing[index].max.ts = data.ts; + options.processing[index].max.val = data.val; + options.processing[index].start.ts = data.ts; + options.processing[index].start.val = data.val; + options.processing[index].end.ts = data.ts; + options.processing[index].end.val = data.val; + } + else { + if (data.val !== null && data.val !== undefined) { + if (data.val > options.processing[index].max.val) { + options.processing[index].max.ts = data.ts; + options.processing[index].max.val = data.val; + } + else if (data.val < options.processing[index].min.val) { + options.processing[index].min.ts = data.ts; + options.processing[index].min.val = data.val; + } + if (data.ts > options.processing[index].end.ts) { + options.processing[index].end.ts = data.ts; + options.processing[index].end.val = data.val; + } + } + else { + if (data.ts > options.processing[index].end.ts) { + options.processing[index].end.ts = data.ts; + options.processing[index].end.val = null; + } + } + } + } + else if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { + options.quantileDataPoints[index].push(data.val || 0); + if (options.logDebug && options.log) { + options.log(`Quantile ${index}: Add ts= ${data.ts} val=${data.val}`); + } + } + else if (options.aggregate === 'integral') { + options.integralDataPoints[index].push(data); + if (options.logDebug && options.log) { + options.log(`Integral ${index}: Add ts= ${data.ts} val=${data.val}`); + } + } + else if (options.aggregate === 'integralTotal') { + options.totalIntegralDataPoints?.push(data); + } +} +/** + * finishAggregationForIntegralEx + * + * Purpose: + * - Calculate integrals per defined time intervals (smart intervals mode). + * - Options must contain `timeIntervals` and `integralDataPoints`. + * - Produces `options.result` as an array with one entry per `timeIntervals` element. + * + * Input: + * - options: InternalHistoryOptions already populated by previous aggregation steps. + * Required fields: + * - timeIntervals: TimeInterval[] (array of intervals to calculate over) + * - integralDataPoints: IobDataEntry[][] where length == timeIntervals.length + 2 + * (one pre-interval array at index 0, one post-interval array at last index) + * - logDebug (optional) and log (optional) for detailed debug output + * + * Output: + * - options.result: IobDataEntry[] of length = timeIntervals.length + * Each entry: + * - ts: representative timestamp (interval midpoint) + * - val: integral value for that interval (number) + * - time: ISO string of ts when logDebug is enabled (optional) + * + * Behaviour / Algorithm overview: + * 1. Find the latest data point that lies in the pre-interval (index 0). + * That point is used as the `current` known value to interpolate forward. + * 2. Initialize finalResult with one entry per time interval; each entry's `val` starts at 0 + * and `ts` is set to the interval midpoint (used as representative timestamp). + * 3. If no pre-interval `current` point exists, nothing to integrate -> return finalResult of zeros. + * 4. Iterate intervals from the first interval that might contain data (index found in step 1): + * - For interval i, obtain the bucket workDP stored at `integralDataPoints[i + 1]`. + * - If bucket has data and `current` exists: + * * Interpolate a point at interval start using linear interpolation between `current` and bucket[0]. + * * Insert that interpolated start point at the beginning of bucket. + * * Update `current` to the last point in this bucket (the newest in that bucket). + * - Find the next known data point `next` in later buckets (search forward) and remember its interval index. + * - If no `next` found, assume no further data -> stop filling remaining intervals. + * - Ensure the bucket has at least a start point; if empty, compute and push a start point interpolated between `current` and `next`. + * - Always compute and push an end point for the interval at `workTi.end - 1`, interpolated between `current` and `next`. + * 5. After all intervals that can be filled have a start and end point, compute the integral for each interval: + * - For each bucket (workDP) compute sum of trapezoidal areas between consecutive points using `calcDiff(...).square`. + * - Set finalResult[i].val = sum for that interval. + * + * Key assumptions and details: + * - `integralDataPoints` array length is `timeIntervals.length + 2`: + * index 0 = pre-interval (values before start), + * indices 1..timeIntervals.length = buckets for each interval, + * index timeIntervals.length + 1 = post-interval (values after end) + * - The function uses linear interpolation (via interpolate2points) to estimate values at interval borders. + * - Interval representative timestamp (`ts`) is computed as start + round((end - start) / 2). + * - End timestamp for each interval when interpolating is `workTi.end - 1` (inclusive millisecond before next interval). + * - If no `current` (no pre-interval data), return early leaving all zeros. + * - If no `next` is found while iterating forward, the function stops filling further intervals, + * because it assumes no future data to interpolate to. + * - Debug logs (if `options.logDebug`) include created interpolated points and interval contents. + * + * Edge cases: + * - Zero-length intervals (end == start) are not expected in `timeIntervals`. + * - If `interpolate2points` gets identical timestamps it returns p1.val (guard in helper). + * - The function mutates `options.integralDataPoints` by inserting start/end interpolated entries. + * - If `workDP` remains undefined for an interval, `finalResult` keeps that interval's value as 0. + */ +function finishAggregationForIntegralEx(options) { + // The first interval is pre-interval: its data is used only to determine the initial "current" value. + let index = 0; + let workDP; + let next = null; + let current = null; + const finalResult = []; + if (!options.integralDataPoints || !options.timeIntervals) { + throw new Error('finishAggregationForIntegralEx: options.integralDataPoints or options.timeIntervals missing'); + } + // 1) Find the first non-empty integral bucket starting from pre-interval (index 0). + // The last element of the first non-empty bucket is the newest value before options.start. + // We must remember, that options.integralDataPoints is longer than options.timeIntervals on 2. It hast pre- and post- values + do { + workDP = options.integralDataPoints[index]; + if (workDP?.length) { + // It must be the newest value before options.start + current = workDP[workDP.length - 1]; + break; + } + index++; + } while (index < options.integralDataPoints.length); + // 2) Initialize finalResult entries for every time interval with ts = midpoint and val = 0 + for (let i = 0; i < options.timeIntervals.length; i++) { + const oneInterval = { + // compute midpoint deterministically: start + round((end - start) / 2) + ts: options.timeIntervals[i].start + + Math.round((options.timeIntervals[i].end - options.timeIntervals[i].start) / 2), + val: 0, + }; + // Add ISO string for easier debugging if requested + oneInterval.time = new Date(oneInterval.ts).toISOString(); + finalResult.push(oneInterval); + } + // assign preliminary result array back to options + options.result = finalResult; + // 3) If there is no value before start, nothing to integrate -> keep zeroed result + if (!current) { + return; + } + let workTi; + // Holds index of the interval where the next known data was found during forward search + let nextIntervalIndex = null; + // 4) Iterate intervals starting from the one where we might have data (index) + // Calculate for every interval the start + for (let i = index; i < options.timeIntervals.length; i++) { + // bucket for interval i is stored at integralDataPoints[i + 1] + workDP = options.integralDataPoints[i + 1]; + workTi = options.timeIntervals[i]; + // If this bucket already contains points, and we have a prior 'current', insert an interpolated start point + if (workDP?.length && current) { + // calculate the first value in this interval + const firstValue = interpolate2points(current, workDP[0], workTi.start); + const time = { ts: workTi.start, val: firstValue }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + // Insert at beginning to make sure bucket starts at interval boundary + workDP.unshift(time); + // Update current to newest in this bucket for subsequent interpolation + current = workDP[workDP.length - 1]; + } + // Find the next known datapoint in later buckets (only search if previous found next is not usable) + if (nextIntervalIndex === null || nextIntervalIndex <= i) { + next = null; + let j = i + 1; + do { + // check bucket j + 1 because of pre- / post-encodings (buckets for intervals are shifted by 1) + if (options.integralDataPoints[j + 1]?.length) { + // next is the earliest datapoint in that later bucket + next = options.integralDataPoints[j + 1][0]; + nextIntervalIndex = j; + break; + } + j++; + } while (j <= options.timeIntervals.length); + } + // If no next datapoint exists, assume no more future data available -> stop filling further intervals + if (!next) { + // We assume, that no more data will come + break; + } + // Ensure the bucket exists + options.integralDataPoints[i + 1] = options.integralDataPoints[i + 1] || []; + workDP = options.integralDataPoints[i + 1]; + // If this bucket is empty, compute an interpolated start point between current and next + if (!workDP.length) { + // place first value + // calculate the first value in this interval + const firstValue = interpolate2points(current, next, workTi.start); + const time = { ts: workTi.start, val: firstValue }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + workDP.push(time); + } + // Always compute an interpolated end point at workTi.end - 1 to make the bucket closed for integral calculation + const lastValue = interpolate2points(current, next, workTi.end - 1); + const time = { ts: workTi.end - 1, val: lastValue }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + workDP.push(time); + } + // 5) Now that start and end points are ensured where possible, compute integral for each interval + for (let i = 0; i < options.timeIntervals.length; i++) { + workDP = options.integralDataPoints[i + 1]; + if (!workDP) { + // leave finalResult[i].val as 0 when no bucket exists + continue; + } + finalResult[i].val = calcIntegralForPeriod(workDP); + } +} +/** + * This function calculates integral for one time period. + */ +function calcIntegralForPeriod(workDP) { + let sum = 0; + for (let i = 0; i < workDP.length - 1; i++) { + sum += calcDiff(workDP[i], workDP[i + 1]).square; + } + return sum; +} +function finishAggregationForIntegral(options) { + let preBorderValueRemoved = false; + let postBorderValueRemoved = false; + const originalResultLength = options.processing.length; + const finalResult = []; + // If timeIntervals are used, delegate to the specialized implementation. + if (options.timeIntervals) { + finishAggregationForIntegralEx(options); + return; + } + if (!options.processing) { + return; + } + // Iterate all processing slots (including pre/post border entries) + for (let k = 0; k < options.processing.length; k++) { + let indexStartTs; + let indexEndTs; + /*if (options.timeIntervals) { + if (k === 0) { + indexEndTs = options.timeIntervals as TimeInterval[][0].start; + indexStartTs = + options.timeIntervals[0].start - (options.timeIntervals[0].end - options.timeIntervals[0].start); + } else if (k >= options.timeIntervals.length) { + indexStartTs = options.timeIntervals[options.timeIntervals.length - 1].end; + indexEndTs = + indexStartTs + (indexStartTs - options.timeIntervals[options.timeIntervals.length - 1].start); + } else { + indexStartTs = options.timeIntervals[k - 1].start; + indexEndTs = options.timeIntervals[k - 1].end; + } + } else */ { + indexStartTs = options.start + (k - 1) * options.step; + indexEndTs = indexStartTs + options.step; + } + const len = options.integralDataPoints[k]?.length; + if (len) { + // Sort data points by ts first + options.integralDataPoints[k].sort(sortByTs); + } + // Make sure that we have entries that always start at the beginning of the interval + if ((!len || options.integralDataPoints[k][0].ts > indexStartTs) && + options.integralDataPoints[k - 1] && + options.integralDataPoints[k - 1][options.integralDataPoints[k - 1].length - 1]) { + // if the first entry of this interval started somewhere in the start of the interval, add a start entry + // same if there is no entry at all in the timeframe, use last entry from interval before + options.integralDataPoints[k] = options.integralDataPoints[k] || []; + const time = { + ts: indexStartTs, + val: options.integralDataPoints[k - 1][options.integralDataPoints[k - 1].length - 1].val, + }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + options.integralDataPoints[k].unshift(time); + if (options.logDebug && options.log) { + options.log(`Integral: ${k}: Added start entry for interval with ts=${indexStartTs}, val=${options.integralDataPoints[k][0].val}`); + } + } + else if (len && options.integralDataPoints[k][0].ts > indexStartTs) { + const time = { + ts: indexStartTs, + val: options.integralDataPoints[k][0].val, + }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + options.integralDataPoints[k].unshift(time); + if (options.logDebug && options.log) { + options.log(`Integral: ${k}: Added start entry for interval with ts=${indexStartTs}, val=${options.integralDataPoints[k][0].val} with same value as first point in interval because no former datapoint was found`); + } + } + else if (len && options.integralDataPoints[k][0].ts < indexStartTs) { + // if the first entry of this interval started before the start of the interval, search for the last value before the start of the interval, add as start entry + let preFirstIndex = null; + for (let kk = 0; kk < options.integralDataPoints[k].length; kk++) { + if (options.integralDataPoints[k][kk].ts >= indexStartTs) { + break; + } + preFirstIndex = kk; + } + if (preFirstIndex !== null) { + const time = { + ts: indexStartTs, + val: options.integralDataPoints[k][preFirstIndex].val, + }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + options.integralDataPoints[k].splice(0, preFirstIndex, time); + if (options.logDebug && options.log) { + options.log(`Integral: ${k}: Remove ${preFirstIndex + 1} entries and add start entry for interval with ts=${indexStartTs}, val=${options.integralDataPoints[k][0].val}`); + } + } + } + // get middle of the time interval + const ts = options.processing[k] !== undefined && options.processing[k].val.ts + ? options.processing[k].val.ts + : Math.round(indexStartTs + (indexEndTs - indexStartTs) / 2); + const point = { + ts, + val: null, + }; + const integralDataPoints = options.integralDataPoints[k] || []; + if (options.logDebug && options.log) { + const vals = integralDataPoints.map(dp => `[${dp.ts}, ${dp.val}]`); + options.log(`Integral: ${k}: ${integralDataPoints.length} data points for interval ${indexStartTs} - ${indexEndTs}: ${vals.join(',')}`); + } + // Calculate Intervals and always calculate till the interval end (start made sure above already) + for (let kk = 0; kk < integralDataPoints.length; kk++) { + // Determine the end timestamp for this segment: next point ts or interval end + const valEndTs = integralDataPoints[kk + 1] + ? Math.min(integralDataPoints[kk + 1].ts, indexEndTs) + : indexEndTs; + // Ignore segments that don't belong or have zero duration + const valDuration = valEndTs - integralDataPoints[kk].ts; + if (valDuration < 0) { + if (options.logDebug && options.log) { + options.log(`Integral: ${k}[${kk}] segment outside interval, ignore ${JSON.stringify(integralDataPoints[kk])} (vs. ${valEndTs})`); + } + break; + } + if (valDuration === 0) { + if (options.logDebug && options.log) { + options.log(`Integral: ${k}[${kk}] zero duration, ignore ${JSON.stringify(integralDataPoints[kk])}`); + } + continue; + } + // Read segment start and end values (treat null as 0) + let valStart = parseFloat(integralDataPoints[kk].val) || 0; + // End value is the next value, or if none, assume "linearity" + let valEnd = parseFloat((integralDataPoints[kk + 1] + ? integralDataPoints[kk + 1].val + : options.integralDataPoints[k + 1] && options.integralDataPoints[k + 1][0] + ? options.integralDataPoints[k + 1][0].val + : valStart)) || 0; + // Accumulate integral according to interpolation mode + if (options.integralInterpolation !== 'linear' || valStart === valEnd) { + // Rectangle approximation: constant value = valStart + const integralAdd = (valStart * valDuration) / options.integralUnit; + // simple rectangle linear interpolation + if (options.logDebug && options.log) { + options.log(`Integral: ${k}[${kk}] : Add ${integralAdd} from val=${valStart} for ${valDuration}`); + } + point.val += integralAdd; + } + else if ((valStart >= 0 && valEnd >= 0) || (valStart <= 0 && valEnd <= 0)) { + // Both values on same side of zero: rectangle + triangle decomposition + let multiplier = 1; + if (valStart <= 0 && valEnd <= 0) { + multiplier = -1; // correct the sign at the end + valStart = -valStart; + valEnd = -valEnd; + } + const minVal = Math.min(valStart, valEnd); + const maxVal = Math.max(valStart, valEnd); + const rectPart = (minVal * valDuration) / options.integralUnit; + const trianglePart = ((maxVal - minVal) * valDuration * 0.5) / options.integralUnit; + const integralAdd = (rectPart + trianglePart) * multiplier; + if (options.logDebug && options.log) { + options.log(`Integral: ${k}[${kk}] : Add R${rectPart} + T${trianglePart} => ${integralAdd} from val=${valStart} to ${valEnd} for ${valDuration}`); + } + point.val += integralAdd; + } + else { + // Values are on different sides of 0, so we need to find the 0 crossing + const zeroCrossing = Math.abs((valStart * valDuration) / (valEnd - valStart)); + // Then calculate two linear segments, one from 0 to the crossing, and one from the crossing to the end + const trianglePart1 = (valStart * zeroCrossing * 0.5) / options.integralUnit; + const trianglePart2 = (valEnd * (valDuration - zeroCrossing) * 0.5) / options.integralUnit; + const integralAdd = trianglePart1 + trianglePart2; + if (options.logDebug && options.log) { + options.log(`Integral: ${k}[${kk}] : Add T${trianglePart1} + T${trianglePart2} => ${integralAdd} from val=${valStart} to ${valEnd} for ${valDuration} (zero crossing ${zeroCrossing})`); + } + point.val += integralAdd; + } + } + /* + options.processing[k] = { + ts: options.processing[k].val.ts, + val: options.processing[k].val.val + } + */ + // If we produced a numeric value, append to final result; otherwise track removed border flags + if (point.val !== null) { + finalResult.push(point); + } + else if (k === 0) { + preBorderValueRemoved = true; + } + else if (k === originalResultLength - 1) { + postBorderValueRemoved = true; + } + } + // If requested, remove pre- / post-border values from the result + if (options.removeBorderValues) { + // we cut out the additional results + if (!preBorderValueRemoved) { + finalResult.splice(0, 1); + } + if (!postBorderValueRemoved) { + finalResult.length--; + } + } + options.result = finalResult; +} +function finishAggregationForMinMax(options) { + if (!options.processing) { + return; + } + let preBorderValueRemoved = false; + let postBorderValueRemoved = false; + const originalResultLength = options.processing.length; + const startIndex = 0; + const endIndex = options.processing.length; + const finalResult = []; + for (let ii = startIndex; ii < endIndex; ii++) { + // it is no one value in this period + if (options.processing[ii] === undefined || options.processing[ii].start.ts === null) { + if (ii === 0) { + preBorderValueRemoved = true; + } + else if (ii === originalResultLength - 1) { + postBorderValueRemoved = true; + } + // options.processing.splice(ii, 1); + continue; + } + // just one value in this period: max == min == start == end + if (options.processing[ii].start.ts === options.processing[ii].end.ts) { + finalResult.push({ + ts: options.processing[ii].start.ts, + val: options.processing[ii].start.val, + }); + } + else if (options.processing[ii].min.ts === options.processing[ii].max.ts) { + // if just 2 values: start == min == max, end + if (options.processing[ii].start.ts === options.processing[ii].min.ts || + options.processing[ii].end.ts === options.processing[ii].min.ts) { + finalResult.push({ + ts: options.processing[ii].start.ts, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts, + val: options.processing[ii].end.val, + }); + } + else { + // if just 3 values: start, min == max, end + finalResult.push({ + ts: options.processing[ii].start.ts, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].max.ts, + val: options.processing[ii].max.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts, + val: options.processing[ii].end.val, + }); + } + } + else if (options.processing[ii].start.ts === options.processing[ii].max.ts) { + // just one value in this period: start == max, min == end + if (options.processing[ii].min.ts === options.processing[ii].end.ts) { + finalResult.push({ + ts: options.processing[ii].start.ts, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts, + val: options.processing[ii].end.val, + }); + } + else { + // start == max, min, end + finalResult.push({ + ts: options.processing[ii].start.ts, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].min.ts, + val: options.processing[ii].min.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts, + val: options.processing[ii].end.val, + }); + } + } + else if (options.processing[ii].end.ts === options.processing[ii].max.ts) { + // just one value in this period: start == min, max == end + if (options.processing[ii].min.ts === options.processing[ii].start.ts) { + finalResult.push({ + ts: options.processing[ii].start.ts, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts, + val: options.processing[ii].end.val, + }); + } + else { + // start, min, max == end + finalResult.push({ + ts: options.processing[ii].start.ts, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].min.ts, + val: options.processing[ii].min.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts, + val: options.processing[ii].end.val, + }); + } + } + else if (options.processing[ii].start.ts === options.processing[ii].min.ts || + options.processing[ii].end.ts === options.processing[ii].min.ts) { + // just one value in this period: start == min, max, end + finalResult.push({ + ts: options.processing[ii].start.ts, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].max.ts, + val: options.processing[ii].max.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts, + val: options.processing[ii].end.val, + }); + } + else { + finalResult.push({ + ts: options.processing[ii].start.ts, + val: options.processing[ii].start.val, + }); + // just one value in this period: start == min, max, end + if (options.processing[ii].max.ts > options.processing[ii].min.ts) { + finalResult.push({ + ts: options.processing[ii].min.ts, + val: options.processing[ii].min.val, + }); + finalResult.push({ + ts: options.processing[ii].max.ts, + val: options.processing[ii].max.val, + }); + } + else { + finalResult.push({ + ts: options.processing[ii].max.ts, + val: options.processing[ii].max.val, + }); + finalResult.push({ + ts: options.processing[ii].min.ts, + val: options.processing[ii].min.val, + }); + } + finalResult.push({ + ts: options.processing[ii].end.ts, + val: options.processing[ii].end.val, + }); + } + } + if (options.removeBorderValues) { + // we cut out the additional results + if (!preBorderValueRemoved) { + finalResult.splice(0, 1); + } + if (!postBorderValueRemoved) { + finalResult.length--; + } + } + options.result = finalResult; +} +function finishAggregationForAverage(options) { + const round = options.round || 100; + let startIndex = 0; + if (!options.processing) { + return; + } + let endIndex = options.processing.length; + const finalResult = []; + if (options.removeBorderValues) { + // we cut out the additional results + // options.processing.splice(0, 1); + // options.averageCount.splice(0, 1); + // options.processing.length--; + // options.averageCount.length--; + startIndex++; + endIndex--; + } + for (let k = startIndex; k < endIndex; k++) { + if (options.processing[k] !== undefined && options.processing[k].val.ts) { + finalResult.push({ + ts: options.processing[k].val.ts, + val: options.processing[k].val.val !== null + ? Math.round((options.processing[k].val.val / options.averageCount[k]) * round) / + round + : null, + }); + } + else { + // no one value in this interval + // options.processing.splice(k, 1); + // options.averageCount.splice(k, 1); // not needed to clean up because not used anymore afterwards + } + } + options.result = finalResult; +} +function finishAggregationForCount(options) { + let startIndex = 0; + if (!options.processing) { + return; + } + let endIndex = options.processing.length; + const finalResult = []; + if (options.removeBorderValues) { + // we cut out the additional results + // options.processing.splice(0, 1); + // options.averageCount.splice(0, 1); + // options.processing.length--; + // options.averageCount.length--; + startIndex++; + endIndex--; + } + for (let k = startIndex; k < endIndex; k++) { + if (options.processing[k] !== undefined && options.processing[k].val.ts) { + finalResult.push({ + ts: options.processing[k].val.ts, + val: options.averageCount[k], + }); + } + else { + // no one value in this interval + // options.processing.splice(k, 1); + // options.averageCount.splice(k, 1); // not needed to clean up because not used anymore afterward + } + } + options.result = finalResult; +} +function finishAggregationPercentile(options) { + let startIndex = 0; + if (!options.processing) { + return; + } + let endIndex = options.processing.length; + const finalResult = []; + if (options.removeBorderValues) { + // we cut out the additional results + /* + options.processing.splice(0, 1); + options.quantileDataPoints.splice(0, 1); + options.processing.length-- + options.quantileDataPoints.length--; + */ + startIndex++; + endIndex--; + } + for (let k = startIndex; k < endIndex; k++) { + if (options.processing[k] !== undefined && options.processing[k].val.ts) { + const point = { + ts: options.processing[k].val.ts, + val: quantile(options.quantile, options.quantileDataPoints[k]), + }; + if (options.logDebug && options.log) { + options.log(`Quantile ${k} ${point.ts}: ${options.quantileDataPoints[k].join(', ')} -> ${point.val}`); + } + finalResult.push(point); + } + else { + // no one value in this interval + // options.processing.splice(k, 1); + // options.quantileDataPoints.splice(k, 1); // not needed to clean up because not used anymore afterward + } + } + options.result = finalResult; +} +function finishAggregationTotalIntegral(options) { + // calculate first entry + if (options.totalIntegralDataPoints?.[0] && options.totalIntegralDataPoints[0].ts !== options.start) { + if (options.totalIntegralDataPoints[0] && + options.totalIntegralDataPoints[0].ts < options.start && + options.totalIntegralDataPoints[1] && + options.totalIntegralDataPoints[1].ts > options.start) { + const y1 = options.totalIntegralDataPoints[0].val; + const y2 = options.totalIntegralDataPoints[1].val; + const x1 = options.totalIntegralDataPoints[0].ts; + const x2 = options.totalIntegralDataPoints[1].ts; + const val = y1 + ((y2 - y1) * (options.start - x1)) / (x2 - x1); + options.totalIntegralDataPoints[0] = { + ts: options.start, + val, + }; + } + } + // calculate last entry + const len = options.totalIntegralDataPoints?.length || 0; + if (options.totalIntegralDataPoints && + options.totalIntegralDataPoints[len - 1] && + options.totalIntegralDataPoints[len - 1].ts !== options.end) { + if (options.totalIntegralDataPoints[len - 1] && + options.totalIntegralDataPoints[len - 1].ts > options.end && + options.totalIntegralDataPoints[len - 2] && + options.totalIntegralDataPoints[len - 2].ts < options.end) { + const y1 = options.totalIntegralDataPoints[len - 2].val; + const y2 = options.totalIntegralDataPoints[len - 1].val; + const x1 = options.totalIntegralDataPoints[len - 2].ts; + const x2 = options.totalIntegralDataPoints[len - 1].ts; + const val = y1 + ((y2 - y1) * (options.start - x1)) / (x2 - x1); + options.totalIntegralDataPoints[len - 1] = { + ts: options.end, + val, + }; + } + else if (options.totalIntegralDataPoints[len - 1] && + options.totalIntegralDataPoints[len - 1].ts < options.end) { + // assume that now we have the same value as before + options.totalIntegralDataPoints.push({ + ts: options.end, + val: options.totalIntegralDataPoints[len - 1].val, + }); + } + } + let integral = 0; + if (options.totalIntegralDataPoints) { + for (let i = 1; i < options.totalIntegralDataPoints.length; i++) { + integral += calcDiff(options.totalIntegralDataPoints[i - 1], options.totalIntegralDataPoints[i]).square; + } + } + options.result = [ + { + ts: options.end, + val: integral, + }, + ]; +} +function finishAggregationForSimple(options) { + let startIndex = 0; + if (!options.processing) { + return; + } + let endIndex = options.processing.length; + const finalResult = []; + if (options.removeBorderValues) { + // we cut out the additional results + // options.processing.splice(0, 1); + // options.processing.length--; + startIndex++; + endIndex--; + } + for (let j = startIndex; j < endIndex; j++) { + if (options.processing[j] !== undefined && options.processing[j].val.ts) { + finalResult.push({ + ts: options.processing[j].val.ts, + val: options.processing[j].val.val, + }); + } + else { + // no one value in this interval + // options.processing.splice(j, 1); + } + } + options.result = finalResult; +} +function finishAggregation(options) { + if (options.aggregate === 'minmax') { + finishAggregationForMinMax(options); + } + else if (options.aggregate === 'average') { + finishAggregationForAverage(options); + } + else if (options.aggregate === 'count') { + finishAggregationForCount(options); + } + else if (options.aggregate === 'integral') { + finishAggregationForIntegral(options); + } + else if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { + finishAggregationPercentile(options); + } + else if (options.aggregate === 'integralTotal') { + finishAggregationTotalIntegral(options); + } + else { + finishAggregationForSimple(options); + } + // free memory + // @ts-expect-error ignore + options.processing = null; + beautify(options); +} +/** + * Beautify the result - remove null values, add start and end values if needed. + * Also round the values if requested and add ID to every value if requested. + */ +function beautify(options) { + if (options.logDebug && options.log) { + options.log(`Beautify: ${options.result?.length} results`); + } + let preFirstValue = null; + let postLastValue = null; + if (options.ignoreNull === 'true') { + // include nulls and replace them with last value + options.ignoreNull = true; + } + else if (options.ignoreNull === 'false') { + // include nulls + options.ignoreNull = false; + } + else if (options.ignoreNull === '0') { + // include nulls and replace them with 0 + options.ignoreNull = 0; + } + else if (options.ignoreNull !== true && options.ignoreNull !== false && options.ignoreNull !== 0) { + options.ignoreNull = false; + } + // process null values, remove points outside the span and find first points after end and before start + if (options.result) { + for (let i = 0; i < options.result.length; i++) { + if (options.ignoreNull !== false) { + // if value is null + if (options.result[i].val === null) { + // null value must be replaced with last not null value + if (options.ignoreNull === true) { + // remove value + options.result.splice(i, 1); + i--; + continue; + } + else { + // null value must be replaced with 0 + options.result[i].val = options.ignoreNull; + } + } + } + // remove all not requested points + if (options.result[i].ts < options.start) { + preFirstValue = options.result[i].val !== null ? options.result[i] : null; + options.result.splice(i, 1); + i--; + continue; + } + postLastValue = options.result[i].val !== null ? options.result[i] : null; + if (options.result[i].ts > options.end) { + options.result.splice(i, options.result.length - i); + break; + } + if (options.round && options.result[i].val && typeof options.result[i].val === 'number') { + options.result[i].val = Math.round(options.result[i].val * options.round) / options.round; + } + } + } + // check start and stop + if (options.result?.length && options.aggregate !== 'none' && !options.removeBorderValues) { + const firstTS = options.result[0].ts; + if (firstTS > options.start && !options.removeBorderValues) { + if (preFirstValue) { + const firstY = options.result[0].val; + // if steps + if (options.aggregate === 'onchange' || !options.aggregate) { + if (preFirstValue.ts !== firstTS) { + options.result.unshift({ ts: options.start, val: preFirstValue.val }); + } + else { + if (options.ignoreNull) { + options.result.unshift({ ts: options.start, val: firstY }); + } + } + } + else { + if (preFirstValue.ts !== firstTS) { + if (firstY !== null) { + // interpolate + const y = preFirstValue.val + + ((firstY - preFirstValue.val) * (options.start - preFirstValue.ts)) / + (firstTS - preFirstValue.ts); + options.result.unshift({ ts: options.start, val: y, i: true }); + if (options.logDebug && options.log) { + options.log(`interpolate ${y} from ${preFirstValue.val} to ${firstY} as first return value`); + } + } + else { + options.result.unshift({ ts: options.start, val: null }); + } + } + else { + if (options.ignoreNull) { + options.result.unshift({ ts: options.start, val: firstY }); + } + } + } + } + else { + if (options.ignoreNull) { + options.result.unshift({ ts: options.start, val: options.result[0].val }); + } + else { + options.result.unshift({ ts: options.start, val: null }); + } + } + } + const lastTS = options.result[options.result.length - 1].ts; + if (lastTS < options.end && !options.removeBorderValues) { + if (postLastValue) { + // if steps + if (options.aggregate === 'onchange' || !options.aggregate) { + // if more data following, draw line to the end of the chart + if (postLastValue.ts !== lastTS) { + options.result.push({ ts: options.end, val: postLastValue.val }); + } + else { + if (options.ignoreNull) { + options.result.push({ ts: options.end, val: postLastValue.val }); + } + } + } + else { + if (postLastValue.ts !== lastTS) { + const lastY = options.result[options.result.length - 1].val; + if (lastY !== null) { + // make interpolation + const _y = lastY + + ((postLastValue.val - lastY) * (options.end - lastTS)) / (postLastValue.ts - lastTS); + options.result.push({ ts: options.end, val: _y, i: true }); + if (options.logDebug && options.log) { + options.log(`interpolate ${_y} from ${lastY} to ${postLastValue.val} as last return value`); + } + } + else { + options.result.push({ ts: options.end, val: null }); + } + } + else { + if (options.ignoreNull) { + options.result.push({ ts: options.end, val: postLastValue.val }); + } + } + } + } + else { + if (options.ignoreNull) { + const lastY = options.result[options.result.length - 1].val; + // if no more data, that means do not draw line + options.result.push({ ts: options.end, val: lastY }); + } + else { + // if no more data, that means do not draw line + options.result.push({ ts: options.end, val: null }); + } + } + } + } + else if (options.aggregate === 'none') { + if (options.count && options.result && options.result.length > options.count) { + options.result.splice(0, options.result.length - options.count); + } + } + if (options.addId && options.result && options.id) { + for (let i = 0; i < options.result.length; i++) { + if (!options.result[i].id) { + options.result[i].id = options.id; + } + } + } +} +/** + * Sends the history to the caller + * + * @param adapter Adapter instance + * @param msg ioBroker message to respond to + * @param id State ID + * @param initialOptions get history options + * @param dataOrError Array or error string + * @param startTime Start time of the request just to measure duration + * @param logId Optional log ID to prefix log messages + */ +function sendResponse(adapter, msg, id, initialOptions, dataOrError, startTime, logId) { + if (typeof dataOrError === 'string') { + adapter.log.error(dataOrError); + adapter.sendTo(msg.from, msg.command, { + result: [], + step: 0, + error: dataOrError, + sessionId: initialOptions.sessionId, + }, msg.callback); + return; + } + let log; + if (logId) { + log = (text) => { + adapter.log.debug(`${logId}: ${text}`); + }; + } + // We now know that dataOrError is IobDataEntry[] + const data = dataOrError; + if (initialOptions.count && !initialOptions.start && data.length > initialOptions.count) { + data.splice(0, data.length - initialOptions.count); + } + if (data[0]) { + let result; + initialOptions.start ||= data[0].ts; + let step = initialOptions.step || 0; + const sourceLength = data.length; + if (!initialOptions.aggregate || initialOptions.aggregate === 'none' || initialOptions.preAggregated) { + const options = initAggregate(initialOptions, id, undefined, log); + options.result = data; + step = 0; + // convert ack from 0/1 to false/true + if (initialOptions.ack) { + for (let i = 0; i < data.length; i++) { + data[i].ack = !!data[i].ack; + } + } + beautify(options); + if (options.aggregate === 'none' && options.count && options.result.length > options.count) { + options.result.splice(0, options.result.length - options.count); + } + result = options.result; + adapter.log.debug(`Send with no aggregation: ${result.length} of: ${sourceLength} in: ${Date.now() - startTime}ms`); + } + else { + const options = initAggregate(initialOptions, id, undefined, log); + aggregation(options, data); + finishAggregation(options); + result = options.result; + adapter.log.debug(`Send after aggregation: ${result.length} of: ${sourceLength} in: ${Date.now() - startTime}ms`); + } + adapter.sendTo(msg.from, msg.command, { + result, + step, + sessionId: initialOptions.sessionId, + }, msg.callback); + } + else { + adapter.log.info('No Data'); + adapter.sendTo(msg.from, msg.command, { result: [], step: null, sessionId: initialOptions.sessionId }, msg.callback); + } +} +function sendResponseCounter(adapter, msg, options, dataOrError) { + // data + // 1586713810000 100 + // 1586713810010 200 + // 1586713810040 500 + // 1586713810050 0 + // 1586713810090 400 + // 1586713810100 0 + // 1586713810110 100 + if (typeof dataOrError === 'string') { + adapter.log.error(dataOrError); + return adapter.sendTo(msg.from, msg.command, { + result: [], + error: dataOrError, + sessionId: options.sessionId, + }, msg.callback); + } + const data = dataOrError; + if (data[0] && data[1]) { + // first | start | afterFirst | ...... | last | end | afterLast + // 5 | | 8 | 9 | 1 | 3 | | 5 + // | 5+(8-5/tsDiff) | | 9 | 1 | | 3+(5-3/tsDiff) | + // (9 - 6.5) + (4 - 1) + if (data[1].ts === options.start) { + data.splice(0, 1); + } + if (data[0].ts < options.start && data[0].val > data[1].val) { + data.splice(0, 1); + } + // interpolate from first to start time + if (data[0].ts < options.start) { + const val = data[0].val + + (data[1].val - data[0].val) * ((options.start - data[0].ts) / (data[1].ts - data[0].ts)); + data.splice(0, 1); + data.unshift({ ts: options.start, val, i: true }); + } + if (data[data.length - 2] !== undefined && data[data.length - 2].ts === options.end) { + data.length--; + } + const veryLast = data[data.length - 1]; + const beforeLast = data[data.length - 2]; + // interpolate from end time to last + if (veryLast !== undefined && beforeLast !== undefined && options.end < veryLast.ts) { + const val = beforeLast.val + + (veryLast.val - beforeLast.val) * ((options.end - beforeLast.ts) / (veryLast.ts - beforeLast.ts)); + data.length--; + data.push({ ts: options.end, val, i: true }); + } + // at this point we expect [6.5, 9, 1, 4] + // at this point we expect [150, 200, 500, 0, 400, 0, 50] + let sum = 0; + if (data.length > 1) { + let val = data[data.length - 1].val; + for (let i = data.length - 2; i >= 0; i--) { + if (data[i].val < val) { + sum += val - data[i].val; + } + val = data[i].val; + } + } + adapter.sendTo(msg.from, msg.command, { result: sum, sessionId: options.sessionId }, msg.callback); + } + else { + adapter.log.info('No Data'); + adapter.sendTo(msg.from, msg.command, { result: 0, step: null, sessionId: options.sessionId }, msg.callback); + } +} +/** + * Get quantile value from an array. + * + * @param q - quantile + * @param list - list of sorted values (ascending) + * @returns Quantile value + */ +function getQuantileValue(q, list) { + if (!q) { + return list[0]; + } + const index = list.length * q; + if (Number.isInteger(index)) { + // mean of two middle numbers + return (list[index - 1] + list[index]) / 2; + } + return list[Math.ceil(index - 1)]; +} +/** + * Calculate quantile for given array of values. + * + * @template T + * @param q - quantile or a list of quantiles + * @param list - array of values + */ +function quantile(q, list) { + list = list.slice().sort(function (a, b) { + a = Number.isNaN(a) ? Number.NEGATIVE_INFINITY : a; + b = Number.isNaN(b) ? Number.NEGATIVE_INFINITY : b; + if (a > b) { + return 1; + } + if (a < b) { + return -1; + } + return 0; + }); + return getQuantileValue(q, list); +} +/** Sort function for IobDataEntry by timestamp */ +function sortByTs(a, b) { + return a.ts - b.ts; +} +//# sourceMappingURL=aggregate.js.map \ No newline at end of file diff --git a/build/lib/aggregate.js.map b/build/lib/aggregate.js.map new file mode 100644 index 00000000..2fdb5bc9 --- /dev/null +++ b/build/lib/aggregate.js.map @@ -0,0 +1 @@ +{"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../../src/lib/aggregate.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/lib/connection-factory.js b/build/lib/connection-factory.js new file mode 100644 index 00000000..1fd479b7 --- /dev/null +++ b/build/lib/connection-factory.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ConnectionFactory = void 0; +class ConnectionFactory { +} +exports.ConnectionFactory = ConnectionFactory; +//# sourceMappingURL=connection-factory.js.map \ No newline at end of file diff --git a/build/lib/connection-factory.js.map b/build/lib/connection-factory.js.map new file mode 100644 index 00000000..f5deba94 --- /dev/null +++ b/build/lib/connection-factory.js.map @@ -0,0 +1 @@ +{"version":3,"file":"connection-factory.js","sourceRoot":"","sources":["../../src/lib/connection-factory.ts"],"names":[],"mappings":";;;AAEA,MAAsB,iBAAiB;CAQtC;AARD,8CAQC"} \ No newline at end of file diff --git a/build/lib/dockerManager.types.js b/build/lib/dockerManager.types.js new file mode 100644 index 00000000..b562bd48 --- /dev/null +++ b/build/lib/dockerManager.types.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=dockerManager.types.js.map \ No newline at end of file diff --git a/build/lib/dockerManager.types.js.map b/build/lib/dockerManager.types.js.map new file mode 100644 index 00000000..2e4ac0f7 --- /dev/null +++ b/build/lib/dockerManager.types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"dockerManager.types.js","sourceRoot":"","sources":["../../src/lib/dockerManager.types.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/build/lib/mssql-client.js b/build/lib/mssql-client.js new file mode 100644 index 00000000..8a88b63d --- /dev/null +++ b/build/lib/mssql-client.js @@ -0,0 +1,64 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MSSQLClientPool = exports.MSSQLClient = void 0; +const connection_factory_1 = require("./connection-factory"); +const sql_client_1 = __importDefault(require("./sql-client")); +const sql_client_pool_1 = require("./sql-client-pool"); +class MSSQLConnectionFactory extends connection_factory_1.ConnectionFactory { + ConnectionPool; + Request; + openConnection(options, callback) { + if (!this.ConnectionPool) { + void import('mssql').then(mssql => { + this.ConnectionPool = mssql.default.ConnectionPool; + this.Request = mssql.default.Request; + this.openConnection(options, callback); + }); + return; + } + const pos = options.server.indexOf(':'); + if (pos !== -1) { + options.port = parseInt(options.server.substring(pos + 1), 10); + options.server = options.server.substring(0, pos); + } + options.pool ||= {}; + options.pool.min = 0; + options.pool.max = 1; + const connection = new this.ConnectionPool(options, (err) => { + if (err) { + callback(new Error(err)); + } + else { + callback(null, connection); + } + }); + } + closeConnection(connection, callback) { + connection.close(callback); + } + execute(connection, sql, callback) { + if (!this.Request) { + throw new Error('Not initialized request'); + } + const request = new this.Request(connection); + request.query(sql, (err, result) => { + callback(err, result?.recordset); + }); + } +} +class MSSQLClient extends sql_client_1.default { + constructor(options) { + super(options, new MSSQLConnectionFactory()); + } +} +exports.MSSQLClient = MSSQLClient; +class MSSQLClientPool extends sql_client_pool_1.SQLClientPool { + constructor(poolOptions, sqlOptions) { + super(poolOptions, sqlOptions, new MSSQLConnectionFactory()); + } +} +exports.MSSQLClientPool = MSSQLClientPool; +//# sourceMappingURL=mssql-client.js.map \ No newline at end of file diff --git a/build/lib/mssql-client.js.map b/build/lib/mssql-client.js.map new file mode 100644 index 00000000..f9450a31 --- /dev/null +++ b/build/lib/mssql-client.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mssql-client.js","sourceRoot":"","sources":["../../src/lib/mssql-client.ts"],"names":[],"mappings":";;;;;;AAAA,6DAAyD;AACzD,8DAAqC;AACrC,uDAAmE;AAMnE,MAAM,sBAAuB,SAAQ,sCAAiB;IAC1C,cAAc,CAAoC;IAClD,OAAO,CAA6B;IAE5C,cAAc,CAAC,OAAqB,EAAE,QAAkE;QACpG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACvB,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;gBAC9B,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;gBACnD,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;gBACrC,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC;YACH,OAAO;QACX,CAAC;QACD,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC/D,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;QAErB,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,GAAmB,EAAQ,EAAE;YAC9E,IAAI,GAAG,EAAE,CAAC;gBACN,QAAQ,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACJ,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAC/B,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;IAED,eAAe,CAAC,UAA0B,EAAE,QAAsC;QAC9E,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,CACH,UAA0B,EAC1B,GAAW,EACX,QAAoE;QAEpE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC/C,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAkB,EAAE,MAAmB,EAAE,EAAE;YAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAED,MAAa,WAAY,SAAQ,oBAAS;IACtC,YAAY,OAAqB;QAC7B,KAAK,CAAC,OAAO,EAAE,IAAI,sBAAsB,EAAE,CAAC,CAAC;IACjD,CAAC;CACJ;AAJD,kCAIC;AAED,MAAa,eAAgB,SAAQ,+BAAa;IAC9C,YAAY,WAAuB,EAAE,UAAwB;QACzD,KAAK,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,sBAAsB,EAAE,CAAC,CAAC;IACjE,CAAC;CACJ;AAJD,0CAIC"} \ No newline at end of file diff --git a/build/lib/mssql.js b/build/lib/mssql.js new file mode 100644 index 00000000..f35e3b41 --- /dev/null +++ b/build/lib/mssql.js @@ -0,0 +1,262 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.init = init; +exports.destroy = destroy; +exports.getFirstTs = getFirstTs; +exports.insert = insert; +exports.retention = retention; +exports.getIdSelect = getIdSelect; +exports.getIdInsert = getIdInsert; +exports.getIdUpdate = getIdUpdate; +exports.getFromSelect = getFromSelect; +exports.getFromInsert = getFromInsert; +exports.getCounterDiff = getCounterDiff; +exports.getHistory = getHistory; +exports.deleteFromTable = deleteFromTable; +exports.update = update; +function init(dbName, doNotCreateDatabase) { + const commands = [ + `CREATE TABLE ${dbName}.dbo.sources (id INTEGER NOT NULL PRIMARY KEY IDENTITY(1,1), name varchar(255));`, + `CREATE TABLE ${dbName}.dbo.datapoints (id INTEGER NOT NULL PRIMARY KEY IDENTITY(1,1), name varchar(255), type INTEGER);`, + `CREATE TABLE ${dbName}.dbo.ts_number (id INTEGER, ts BIGINT, val REAL, ack BIT, _from INTEGER, q INTEGER);`, + `CREATE INDEX i_id on ${dbName}.dbo.ts_number (id, ts);`, + `CREATE TABLE ${dbName}.dbo.ts_string (id INTEGER, ts BIGINT, val TEXT, ack BIT, _from INTEGER, q INTEGER);`, + `CREATE INDEX i_id on ${dbName}.dbo.ts_string (id, ts);`, + `CREATE TABLE ${dbName}.dbo.ts_bool (id INTEGER, ts BIGINT, val BIT, ack BIT, _from INTEGER, q INTEGER);`, + `CREATE INDEX i_id on ${dbName}.dbo.ts_bool (id, ts);`, + `CREATE TABLE ${dbName}.dbo.ts_counter (id INTEGER, ts BIGINT, val REAL);`, + `CREATE INDEX i_id on ${dbName}.dbo.ts_counter (id, ts);`, + ]; + if (!doNotCreateDatabase) { + commands.unshift(`CREATE DATABASE ${dbName};`); + } + return commands; +} +function destroy(dbName) { + return [ + `DROP TABLE ${dbName}.dbo.ts_counter;`, + `DROP TABLE ${dbName}.dbo.ts_number;`, + `DROP TABLE ${dbName}.dbo.ts_string;`, + `DROP TABLE ${dbName}.dbo.ts_bool;`, + `DROP TABLE ${dbName}.dbo.sources;`, + `DROP TABLE ${dbName}.dbo.datapoints;`, + `DROP DATABASE ${dbName};`, + `DBCC FREEPROCCACHE;`, + ]; +} +function getFirstTs(dbName, table) { + return `SELECT id, MIN(ts) AS ts FROM ${dbName}.dbo.${table} GROUP BY id;`; +} +function insert(dbName, index, values) { + const insertValues = {}; + values.forEach(value => { + // state, from, db + insertValues[value.table] ||= []; + if (!value.state || value.state.val === null || value.state.val === undefined) { + value.state.val = 'NULL'; + } + else if (value.table === 'ts_string') { + value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; + } + else if (value.table === 'ts_bool') { + value.state.val = value.state.val ? 1 : 0; + } + else if (value.table === 'ts_number') { + if (isNaN(value.state.val)) { + value.state.val = 'NULL'; + } + } + if (value.table === 'ts_counter') { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val})`); + } + else { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val}, ${value.state.ack ? 1 : 0}, ${value.from || 0}, ${value.state.q || 0})`); + } + }); + const query = []; + for (const table in insertValues) { + if (table === 'ts_counter') { + while (insertValues[table].length) { + query.push(`INSERT INTO ${dbName}.dbo.ts_counter (id, ts, val) VALUES ${insertValues[table].splice(0, 500).join(',')};`); + } + } + else { + while (insertValues[table].length) { + query.push(`INSERT INTO ${dbName}.dbo.${table} (id, ts, val, ack, _from, q) VALUES ${insertValues[table].splice(0, 500).join(',')};`); + } + } + } + return query.join(' '); +} +function retention(dbName, index, table, retention) { + const d = new Date(); + d.setSeconds(-retention); + let query = `DELETE FROM ${dbName}.dbo.${table} WHERE`; + query += ` id=${index}`; + query += ` AND ts < ${d.getTime()}`; + query += ';'; + return query; +} +function getIdSelect(dbName, name) { + if (!name) { + return `SELECT id, type, name FROM ${dbName}.dbo.datapoints;`; + } + return `SELECT id, type, name FROM ${dbName}.dbo.datapoints WHERE name='${name}';`; +} +function getIdInsert(dbName, name, type) { + return `INSERT INTO ${dbName}.dbo.datapoints (name, type) VALUES('${name}', ${type});`; +} +function getIdUpdate(dbName, id, type) { + return `UPDATE ${dbName}.dbo.datapoints SET type=${type} WHERE id=${id};`; +} +function getFromSelect(dbName, name) { + if (name) { + return `SELECT id FROM ${dbName}.dbo.sources WHERE name='${name}';`; + } + return `SELECT id, name FROM ${dbName}.dbo.sources;`; +} +function getFromInsert(dbName, values) { + return `INSERT INTO ${dbName}.dbo.sources (name) VALUES('${values}');`; +} +function getCounterDiff(dbName, options) { + // Take first real value after start + const subQueryStart = `SELECT TOP 1 val, ts FROM ${dbName}.dbo.ts_number WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts>=${options.start} AND ${dbName}.dbo.ts_number.ts<${options.end} AND ${dbName}.dbo.ts_number.val IS NOT NULL ORDER BY ${dbName}.dbo.ts_number.ts ASC`; + // Take last real value before the end + const subQueryEnd = `SELECT TOP 1 val, ts FROM ${dbName}.dbo.ts_number WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts>=${options.start} AND ${dbName}.dbo.ts_number.ts<${options.end} AND ${dbName}.dbo.ts_number.val IS NOT NULL ORDER BY ${dbName}.dbo.ts_number.ts DESC`; + // Take last value before start + const subQueryFirst = `SELECT TOP 1 val, ts FROM ${dbName}.dbo.ts_number WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts< ${options.start} ORDER BY ${dbName}.dbo.ts_number.ts DESC`; + // Take next value after end + const subQueryLast = `SELECT TOP 1 val, ts FROM ${dbName}.dbo.ts_number WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts>=${options.end} ORDER BY ${dbName}.dbo.ts_number.ts ASC`; + // get values from counters where counter values changed + const subQueryCounterChanges = `SELECT val, ts FROM ${dbName}.dbo.ts_counter WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts>${options.start} AND ${dbName}.dbo.ts_number.ts<${options.end} AND ${dbName}.dbo.ts_number.val IS NOT NULL ORDER BY ${dbName}.dbo.ts_number.ts ASC`; + return (`${subQueryFirst} ` + + `UNION ALL (${subQueryStart}) a ` + + `UNION ALL (${subQueryEnd}) b ` + + `UNION ALL (${subQueryLast}) c` + + `UNION ALL (${subQueryCounterChanges}) d`); +} +function getHistory(dbName, table, options) { + let query = 'SELECT * FROM (SELECT '; + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + query += ` TOP ${options.count}`; + } + query += ` ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? `, ack` : ''}${options.from ? `, ${dbName}.dbo.sources.name as 'from'` : ''}${options.q ? `, q` : ''} FROM ${dbName}.dbo.${table}`; + if (options.from) { + query += ` INNER JOIN ${dbName}.dbo.sources ON ${dbName}.dbo.sources.id=${dbName}.dbo.${table}._from`; + } + let where = ''; + if (options.index !== null) { + where += ` ${dbName}.dbo.${table}.id=${options.index}`; + } + if (options.end) { + where += `${where ? ` AND` : ''} ${dbName}.dbo.${table}.ts < ${options.end}`; + } + if (options.start) { + where += `${where ? ` AND` : ''} ${dbName}.dbo.${table}.ts >= ${options.start}`; + } + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + where += ` ORDER BY ts`; + if ((!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries)) { + where += ` DESC`; + } + else { + where += ` ASC`; + } + } + where += `) AS t`; + if (options.start) { + // add last value before start + let subQuery; + let subWhere; + subQuery = ` SELECT TOP 1 ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? `, ack` : ''}${options.from ? `, ${dbName}.dbo.sources.name as 'from'` : ''}${options.q ? `, q` : ''} FROM ${dbName}.dbo.${table}`; + if (options.from) { + subQuery += ` INNER JOIN ${dbName}.dbo.sources ON ${dbName}.dbo.sources.id=${dbName}.dbo.${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${dbName}.dbo.${table}.id=${options.index}`; + } + if (options.ignoreNull) { + //subWhere += (subWhere ? ` AND` : '') + ` val <> NULL`; + } + subWhere += `${subWhere ? ` AND` : ''} ${dbName}.dbo.${table}.ts < ${options.start}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${dbName}.dbo.${table}.ts DESC`; + where += ` UNION ALL SELECT * FROM (${subQuery}) a`; + // add next value after end + subQuery = ` SELECT TOP 1 ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? `, ack` : ''}${options.from ? `, ${dbName}.dbo.sources.name as 'from'` : ''}${options.q ? `, q` : ''} FROM ${dbName}.dbo.${table}`; + if (options.from) { + subQuery += ` INNER JOIN ${dbName}.dbo.sources ON ${dbName}.dbo.sources.id=${dbName}.dbo.${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${dbName}.dbo.${table}.id=${options.index}`; + } + if (options.ignoreNull) { + //subWhere += (subWhere ? ` AND` : '') + ` val <> NULL`; + } + subWhere += `${subWhere ? ` AND` : ''} ${dbName}.dbo.${table}.ts >= ${options.end}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${dbName}.dbo.${table}.ts ASC`; + where += ` UNION ALL SELECT * FROM (${subQuery}) b`; + } + if (where) { + query += ` WHERE ${where}`; + } + query += ` ORDER BY ts`; + if ((!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries)) { + query += ` DESC`; + } + else { + query += ` ASC`; + } + query += `;`; + return query; +} +function deleteFromTable(dbName, table, index, start, end) { + let query = `DELETE FROM ${dbName}.dbo.${table} WHERE`; + query += ` id=${index}`; + if (start && end) { + query += ` AND ${dbName}.dbo.${table}.ts>=${start} AND ${dbName}.dbo.${table}.ts<=${end}`; + } + else if (start) { + query += ` AND ${dbName}.dbo.${table}.ts=${start}`; + } + query += ';'; + return query; +} +function update(dbName, index, state, from, table) { + if (!state || state.val === null || state.val === undefined) { + state.val = 'NULL'; + } + else if (table === 'ts_bool') { + state.val = state.val ? 1 : 0; + } + else if (table === 'ts_string') { + state.val = `'${state.val.toString().replace(/'/g, '')}'`; + } + let query = `UPDATE ${dbName}.dbo.${table} SET `; + const vals = []; + if (state.val !== undefined) { + vals.push(`val=${state.val}`); + } + if (state.q !== undefined) { + vals.push(`q=${state.q}`); + } + if (from !== undefined) { + vals.push(`_from=${from}`); + } + if (state.ack !== undefined) { + vals.push(`ack=${state.ack ? 1 : 0}`); + } + query += vals.join(', '); + query += ` WHERE id=${index} AND ts=${state.ts};`; + return query; +} +//# sourceMappingURL=mssql.js.map \ No newline at end of file diff --git a/build/lib/mssql.js.map b/build/lib/mssql.js.map new file mode 100644 index 00000000..3c950117 --- /dev/null +++ b/build/lib/mssql.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mssql.js","sourceRoot":"","sources":["../../src/lib/mssql.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/lib/mysql-client.js b/build/lib/mysql-client.js new file mode 100644 index 00000000..3dac141e --- /dev/null +++ b/build/lib/mysql-client.js @@ -0,0 +1,52 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MySQL2ClientPool = exports.MySQL2Client = void 0; +const connection_factory_1 = require("./connection-factory"); +const sql_client_1 = __importDefault(require("./sql-client")); +const sql_client_pool_1 = require("./sql-client-pool"); +class MySQL2ConnectionFactory extends connection_factory_1.ConnectionFactory { + createConnection; + openConnection(options, callback) { + if (!this.createConnection) { + void import('mysql2').then(mysql2 => { + this.createConnection = mysql2.default.createConnection; + this.openConnection(options, callback); + }); + return; + } + const connection = this.createConnection(options); + connection.connect((err) => callback(err, connection)); + } + closeConnection(connection, callback) { + if (connection) { + connection.end(callback); + } + else { + callback?.(null); + } + } + execute(connection, sql, callback) { + connection.execute(sql, (err, results) => { + if (err) { + return callback(err); + } + return callback(null, results); + }); + } +} +class MySQL2Client extends sql_client_1.default { + constructor(sqlConnection) { + super(sqlConnection, new MySQL2ConnectionFactory()); + } +} +exports.MySQL2Client = MySQL2Client; +class MySQL2ClientPool extends sql_client_pool_1.SQLClientPool { + constructor(poolOptions, sqlOptions) { + super(poolOptions, sqlOptions, new MySQL2ConnectionFactory()); + } +} +exports.MySQL2ClientPool = MySQL2ClientPool; +//# sourceMappingURL=mysql-client.js.map \ No newline at end of file diff --git a/build/lib/mysql-client.js.map b/build/lib/mysql-client.js.map new file mode 100644 index 00000000..00f723c3 --- /dev/null +++ b/build/lib/mysql-client.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mysql-client.js","sourceRoot":"","sources":["../../src/lib/mysql-client.ts"],"names":[],"mappings":";;;;;;AAAA,6DAA6E;AAC7E,8DAAqC;AACrC,uDAAmE;AAMnE,MAAM,uBAAwB,SAAQ,sCAAiB;IAC3C,gBAAgB,CAAM;IAC9B,cAAc,CAAC,OAAqB,EAAE,QAA6D;QAC/F,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACzB,KAAK,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;gBAChC,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC;gBACxD,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC;YACH,OAAO;QACX,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAClD,UAAU,CAAC,OAAO,CAAC,CAAC,GAAiB,EAAQ,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IAC/E,CAAC;IAED,eAAe,CAAC,UAAyC,EAAE,QAAwC;QAC/F,IAAI,UAAU,EAAE,CAAC;YACb,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACJ,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACL,CAAC;IAED,OAAO,CACH,UAAyB,EACzB,GAAW,EACX,QAAqE;QAErE,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAA6B,EAAE,OAAiB,EAAE,EAAE;YACzE,IAAI,GAAG,EAAE,CAAC;gBACN,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;YACD,OAAO,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAED,MAAa,YAAa,SAAQ,oBAAS;IACvC,YAAY,aAA2B;QACnC,KAAK,CAAC,aAAa,EAAE,IAAI,uBAAuB,EAAE,CAAC,CAAC;IACxD,CAAC;CACJ;AAJD,oCAIC;AAED,MAAa,gBAAiB,SAAQ,+BAAa;IAC/C,YAAY,WAAuB,EAAE,UAAwB;QACzD,KAAK,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,uBAAuB,EAAE,CAAC,CAAC;IAClE,CAAC;CACJ;AAJD,4CAIC"} \ No newline at end of file diff --git a/build/lib/mysql.js b/build/lib/mysql.js new file mode 100644 index 00000000..27a3f3a6 --- /dev/null +++ b/build/lib/mysql.js @@ -0,0 +1,239 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.init = init; +exports.destroy = destroy; +exports.getFirstTs = getFirstTs; +exports.insert = insert; +exports.retention = retention; +exports.getIdSelect = getIdSelect; +exports.getIdInsert = getIdInsert; +exports.getIdUpdate = getIdUpdate; +exports.getFromSelect = getFromSelect; +exports.getFromInsert = getFromInsert; +exports.getCounterDiff = getCounterDiff; +exports.getHistory = getHistory; +exports.deleteFromTable = deleteFromTable; +exports.update = update; +function init(dbName, doNotCreateDatabase) { + const commands = [ + `CREATE TABLE \`${dbName}\`.sources (id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, name TEXT);`, + `CREATE TABLE \`${dbName}\`.datapoints (id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, name TEXT, type INTEGER);`, + `CREATE TABLE \`${dbName}\`.ts_number (id INTEGER, ts BIGINT, val REAL, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));`, + `CREATE TABLE \`${dbName}\`.ts_string (id INTEGER, ts BIGINT, val TEXT, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));`, + `CREATE TABLE \`${dbName}\`.ts_bool (id INTEGER, ts BIGINT, val BOOLEAN, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));`, + `CREATE TABLE \`${dbName}\`.ts_counter (id INTEGER, ts BIGINT, val REAL);`, + ]; + !doNotCreateDatabase && + commands.unshift(`CREATE DATABASE \`${dbName}\` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;`); + return commands; +} +function destroy(dbName) { + return [ + `DROP TABLE \`${dbName}\`.ts_counter;`, + `DROP TABLE \`${dbName}\`.ts_number;`, + `DROP TABLE \`${dbName}\`.ts_string;`, + `DROP TABLE \`${dbName}\`.ts_bool;`, + `DROP TABLE \`${dbName}\`.sources;`, + `DROP TABLE \`${dbName}\`.datapoints;`, + `DROP DATABASE \`${dbName}\`;`, + ]; +} +function getFirstTs(dbName, table) { + return `SELECT id, MIN(ts) AS ts FROM \`${dbName}\`.${table} GROUP BY id;`; +} +function insert(dbName, index, values) { + const insertValues = {}; + values.forEach(value => { + // state, from, db + insertValues[value.table] = insertValues[value.table] || []; + if (!value.state || value.state.val === null || value.state.val === undefined) { + value.state.val = 'NULL'; + } + else if (value.table === 'ts_string') { + value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; + } + else if (value.table === 'ts_number') { + if (isNaN(value.state.val)) { + value.state.val = 'NULL'; + } + } + if (value.table === 'ts_counter') { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val})`); + } + else { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val}, ${value.state.ack ? 1 : 0}, ${value.from || 0}, ${value.state.q || 0})`); + } + }); + const query = []; + for (const table in insertValues) { + if (table === 'ts_counter') { + while (insertValues[table].length) { + query.push(`INSERT INTO \`${dbName}\`.ts_counter (id, ts, val) VALUES ${insertValues[table].splice(0, 500).join(',')};`); + } + } + else { + while (insertValues[table].length) { + query.push(`INSERT INTO \`${dbName}\`.${table} (id, ts, val, ack, _from, q) VALUES ${insertValues[table].splice(0, 500).join(',')};`); + } + } + } + return query.join(' '); +} +function retention(dbName, index, table, retention) { + const d = new Date(); + d.setSeconds(-retention); + let query = `DELETE FROM \`${dbName}\`.${table} WHERE`; + query += ` id=${index}`; + query += ` AND ts < ${d.getTime()}`; + query += ';'; + return query; +} +function getIdSelect(dbName, name) { + if (!name) { + return `SELECT id, type, name FROM \`${dbName}\`.datapoints;`; + } + return `SELECT id, type, name FROM \`${dbName}\`.datapoints WHERE name='${name}';`; +} +function getIdInsert(dbName, name, type) { + return `INSERT INTO \`${dbName}\`.datapoints (name, type) VALUES('${name}', ${type});`; +} +function getIdUpdate(dbName, id, type) { + return `UPDATE \`${dbName}\`.datapoints SET type=${type} WHERE id=${id};`; +} +function getFromSelect(dbName, name) { + if (name) { + return `SELECT id FROM \`${dbName}\`.sources WHERE name='${name}';`; + } + return `SELECT id, name FROM \`${dbName}\`.sources;`; +} +function getFromInsert(dbName, values) { + return `INSERT INTO \`${dbName}\`.sources (name) VALUES('${values}');`; +} +function getCounterDiff(dbName, options) { + // Take first real value after start + const subQueryStart = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC LIMIT 1`; + // Take last real value before the end + const subQueryEnd = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts DESC LIMIT 1`; + // Take last value before start + const subQueryFirst = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts< ${options.start} ORDER BY ts DESC LIMIT 1`; + // Take next value after end + const subQueryLast = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>= ${options.end} ORDER BY ts ASC LIMIT 1`; + // get values from counters where counter changed from up to down (e.g. counter changed) + const subQueryCounterChanges = `SELECT ts, val FROM \`${dbName}\`.ts_counter WHERE id=${options.index} AND ts>${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC`; + return (`SELECT DISTINCT(a.ts), a.val from ((${subQueryFirst})\n` + + `UNION ALL \n(${subQueryStart})\n` + + `UNION ALL \n(${subQueryEnd})\n` + + `UNION ALL \n(${subQueryLast})\n` + + `UNION ALL \n(${subQueryCounterChanges})\n` + + `ORDER BY ts) a;`); +} +function getHistory(dbName, table, options) { + let query = `SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${options.from ? `, \`${dbName}\`.sources.name as 'from'` : ''}${options.q ? ', q' : ''} FROM \`${dbName}\`.${table}`; + if (options.from) { + query += ` INNER JOIN \`${dbName}\`.sources ON \`${dbName}\`.sources.id=\`${dbName}\`.${table}._from`; + } + let where = ''; + if (options.index !== null) { + where += ` \`${dbName}\`.${table}.id=${options.index}`; + } + if (options.end) { + where += `${where ? ' AND' : ''} \`${dbName}\`.${table}.ts < ${options.end}`; + } + if (options.start) { + where += `${where ? ' AND' : ''} \`${dbName}\`.${table}.ts >= ${options.start}`; + let subQuery; + let subWhere; + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${options.from ? `, \`${dbName}\`.sources.name as 'from'` : ''}${options.q ? ', q' : ''} FROM \`${dbName}\`.${table}`; + if (options.from) { + subQuery += ` INNER JOIN \`${dbName}\`.sources ON \`${dbName}\`.sources.id=\`${dbName}\`.${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` \`${dbName}\`.${table}.id=${options.index}`; + } + if (options.ignoreNull) { + // subWhere += (subWhere ? " AND" : "") + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} \`${dbName}\`.${table}.ts < ${options.start}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY \`${dbName}\`.${table}.ts DESC LIMIT 1`; + where += ` UNION ALL (${subQuery})`; + // add next value after end + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${options.from ? `, \`${dbName}\`.sources.name as 'from'` : ''}${options.q ? ', q' : ''} FROM \`${dbName}\`.${table}`; + if (options.from) { + subQuery += ` INNER JOIN \`${dbName}\`.sources ON \`${dbName}\`.sources.id=\`${dbName}\`.${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` \`${dbName}\`.${table}.id=${options.index}`; + } + if (options.ignoreNull) { + // subWhere += (subWhere ? " AND" : "") + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} \`${dbName}\`.${table}.ts >= ${options.end}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY \`${dbName}\`.${table}.ts ASC LIMIT 1`; + where += ` UNION ALL (${subQuery})`; + } + if (where) { + query += ` WHERE ${where}`; + } + query += ' ORDER BY ts'; + if ((!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries)) { + query += ' DESC'; + } + else { + query += ' ASC'; + } + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + query += ` LIMIT ${options.count + 2}`; + } + query += ';'; + return query; +} +function deleteFromTable(dbName, table, index, start, end) { + let query = `DELETE FROM \`${dbName}\`.${table} WHERE`; + query += ` id=${index}`; + if (start && end) { + query += ` AND ts>=${start} AND ts<=${end}`; + } + else if (start) { + query += ` AND ts=${start}`; + } + query += ';'; + return query; +} +function update(dbName, index, state, from, table) { + if (!state || state.val === null || state.val === undefined) { + state.val = 'NULL'; + } + else if (table === 'ts_string') { + state.val = `'${state.val.toString().replace(/'/g, '')}'`; + } + let query = `UPDATE \`${dbName}\`.${table} SET `; + const vals = []; + if (state.val !== undefined) { + vals.push(`val=${state.val}`); + } + if (state.q !== undefined) { + vals.push(`q=${state.q}`); + } + if (from !== undefined) { + vals.push(`_from=${from}`); + } + if (state.ack !== undefined) { + vals.push(`ack=${state.ack ? 1 : 0}`); + } + query += vals.join(', '); + query += ' WHERE '; + query += ` id=${index}`; + query += ` AND ts=${state.ts}`; + query += ';'; + return query; +} +//# sourceMappingURL=mysql.js.map \ No newline at end of file diff --git a/build/lib/mysql.js.map b/build/lib/mysql.js.map new file mode 100644 index 00000000..e71b7fc9 --- /dev/null +++ b/build/lib/mysql.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mysql.js","sourceRoot":"","sources":["../../src/lib/mysql.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/lib/postgresql-client.js b/build/lib/postgresql-client.js new file mode 100644 index 00000000..d9c3c5e1 --- /dev/null +++ b/build/lib/postgresql-client.js @@ -0,0 +1,53 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PostgreSQLClientPool = exports.PostgreSQLClient = void 0; +const connection_factory_1 = require("./connection-factory"); +const sql_client_1 = __importDefault(require("./sql-client")); +const sql_client_pool_1 = require("./sql-client-pool"); +// PostgreSQLConnectionFactory does not use any of node-pg's built-in pooling. +class PostgreSQLConnectionFactory extends connection_factory_1.ConnectionFactory { + Client; + openConnection(connectString, callback) { + if (!this.Client) { + void import('pg').then(pg => { + this.Client = pg.default.native?.Client || pg.default.Client; + this.openConnection(connectString, callback); + }); + return; + } + const connection = new this.Client(connectString); + connection.connect(err => callback(err, connection)); + } + closeConnection(connection, callback) { + if (connection) { + connection.end(callback); + } + else { + callback?.(null); + } + } + execute(connection, sql, callback) { + connection.query(sql, (err, results) => { + if (err) { + return callback(err); + } + return callback(null, results?.rows); + }); + } +} +class PostgreSQLClient extends sql_client_1.default { + constructor(connectString) { + super(connectString, new PostgreSQLConnectionFactory()); + } +} +exports.PostgreSQLClient = PostgreSQLClient; +class PostgreSQLClientPool extends sql_client_pool_1.SQLClientPool { + constructor(poolOptions, connectString) { + super(poolOptions, connectString, new PostgreSQLConnectionFactory()); + } +} +exports.PostgreSQLClientPool = PostgreSQLClientPool; +//# sourceMappingURL=postgresql-client.js.map \ No newline at end of file diff --git a/build/lib/postgresql-client.js.map b/build/lib/postgresql-client.js.map new file mode 100644 index 00000000..d61c84a7 --- /dev/null +++ b/build/lib/postgresql-client.js.map @@ -0,0 +1 @@ +{"version":3,"file":"postgresql-client.js","sourceRoot":"","sources":["../../src/lib/postgresql-client.ts"],"names":[],"mappings":";;;;;;AAAA,6DAAyD;AACzD,8DAAqC;AACrC,uDAAmE;AAMnE,8EAA8E;AAC9E,MAAM,2BAA4B,SAAQ,sCAAiB;IAC/C,MAAM,CAA4B;IAE1C,cAAc,CAAC,aAAgC,EAAE,QAAyD;QACtG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;gBACxB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;gBAC7D,IAAI,CAAC,cAAc,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;YACjD,CAAC,CAAC,CAAC;YACH,OAAO;QACX,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAClD,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,eAAe,CAAC,UAAqC,EAAE,QAAwC;QAC3F,IAAI,UAAU,EAAE,CAAC;YACb,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACJ,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACL,CAAC;IAED,OAAO,CACH,UAAkB,EAClB,GAAW,EACX,QAAqE;QAErE,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAU,EAAE,OAAoB,EAAQ,EAAE;YAC7D,IAAI,GAAG,EAAE,CAAC;gBACN,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;YACD,OAAO,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAED,MAAa,gBAAiB,SAAQ,oBAAS;IAC3C,YAAY,aAAgC;QACxC,KAAK,CAAC,aAAa,EAAE,IAAI,2BAA2B,EAAE,CAAC,CAAC;IAC5D,CAAC;CACJ;AAJD,4CAIC;AAED,MAAa,oBAAqB,SAAQ,+BAAa;IACnD,YAAY,WAAuB,EAAE,aAAgC;QACjE,KAAK,CAAC,WAAW,EAAE,aAAa,EAAE,IAAI,2BAA2B,EAAE,CAAC,CAAC;IACzE,CAAC;CACJ;AAJD,oDAIC"} \ No newline at end of file diff --git a/build/lib/postgresql.js b/build/lib/postgresql.js new file mode 100644 index 00000000..388d945e --- /dev/null +++ b/build/lib/postgresql.js @@ -0,0 +1,236 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.init = init; +exports.destroy = destroy; +exports.getFirstTs = getFirstTs; +exports.insert = insert; +exports.retention = retention; +exports.getIdSelect = getIdSelect; +exports.getIdInsert = getIdInsert; +exports.getIdUpdate = getIdUpdate; +exports.getFromSelect = getFromSelect; +exports.getFromInsert = getFromInsert; +exports.getCounterDiff = getCounterDiff; +exports.getHistory = getHistory; +exports.deleteFromTable = deleteFromTable; +exports.update = update; +function init(_dbName, _doNotCreateDatabase) { + return [ + 'CREATE TABLE sources (id SERIAL NOT NULL PRIMARY KEY, name TEXT);', + 'CREATE TABLE datapoints (id SERIAL NOT NULL PRIMARY KEY, name TEXT, type INTEGER);', + 'CREATE TABLE ts_number (id INTEGER NOT NULL, ts BIGINT, val REAL, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_string (id INTEGER NOT NULL, ts BIGINT, val TEXT, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_bool (id INTEGER NOT NULL, ts BIGINT, val BOOLEAN, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_counter (id INTEGER NOT NULL, ts BIGINT, val REAL);', + ]; +} +function destroy(_dbName) { + return [ + 'DROP TABLE ts_counter;', + 'DROP TABLE ts_number;', + 'DROP TABLE ts_string;', + 'DROP TABLE ts_bool;', + 'DROP TABLE sources;', + 'DROP TABLE datapoints;', + ]; +} +function getFirstTs(_dbName, table) { + return `SELECT id, MIN(ts) AS ts FROM ${table} GROUP BY id;`; +} +function insert(_dbName, index, values) { + const insertValues = {}; + values.forEach(value => { + // state, from, table + insertValues[value.table] = insertValues[value.table] || []; + if (!value.state || value.state.val === null || value.state.val === undefined) { + value.state.val = 'NULL'; + } + else if (value.table === 'ts_string') { + value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; + } + else if (value.table === 'ts_number') { + if (isNaN(value.state.val)) { + value.state.val = 'NULL'; + } + } + if (value.table === 'ts_counter') { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val})`); + } + else { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val}, ${!!value.state.ack}, ${value.from || 0}, ${value.state.q || 0})`); + } + }); + const query = []; + for (const table in insertValues) { + if (table === 'ts_counter') { + while (insertValues[table].length) { + query.push(`INSERT INTO ts_counter (id, ts, val) VALUES ${insertValues[table].splice(0, 500).join(',')};`); + } + } + else { + while (insertValues[table].length) { + query.push(`INSERT INTO ${table} (id, ts, val, ack, _from, q) VALUES ${insertValues[table].splice(0, 500).join(',')};`); + } + } + } + return query.join(' '); +} +function retention(_dbName, index, table, retention) { + const d = new Date(); + d.setSeconds(-retention); + let query = `DELETE FROM ${table} WHERE`; + query += ` id=${index}`; + query += ` AND ts < ${d.getTime()}`; + query += ';'; + return query; +} +function getIdSelect(_dbName, name) { + if (!name) { + return 'SELECT id, type, name FROM datapoints;'; + } + return `SELECT id, type, name FROM datapoints WHERE name='${name}';`; +} +function getIdInsert(_dbName, name, type) { + return `INSERT INTO datapoints (name, type) VALUES('${name}', ${type});`; +} +function getIdUpdate(_dbName, id, type) { + return `UPDATE datapoints SET type = ${type} WHERE id = ${id};`; +} +function getFromSelect(_dbName, name) { + if (name) { + return `SELECT id FROM sources WHERE name='${name}';`; + } + return 'SELECT id, name FROM sources;'; +} +function getFromInsert(dbName, values) { + return `INSERT INTO sources (name) VALUES('${values}');`; +} +function getCounterDiff(dbName, options) { + // Take first real value after start + const subQueryStart = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC LIMIT 1`; + // Take last real value before the end + const subQueryEnd = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts DESC LIMIT 1`; + // Take last value before start + const subQueryFirst = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts< ${options.start} ORDER BY ts DESC LIMIT 1`; + // Take next value after end + const subQueryLast = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>= ${options.end} ORDER BY ts ASC LIMIT 1`; + // get values from counters where counter changed from up to down (e.g. counter changed) + const subQueryCounterChanges = `SELECT ts, val FROM \`${dbName}\`.ts_counter WHERE id=${options.index} AND ts>${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC`; + return (`SELECT DISTINCT(a.ts), a.val from ((${subQueryFirst})\n` + + `UNION ALL \n(${subQueryStart})\n` + + `UNION ALL \n(${subQueryEnd})\n` + + `UNION ALL \n(${subQueryLast})\n` + + `UNION ALL \n(${subQueryCounterChanges})\n` + + `ORDER BY ts) a;`); +} +function getHistory(_dbName, table, options) { + let query = `SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${options.from ? ', sources.name as from' : ''}${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + query += ` INNER JOIN sources ON sources.id=${table}._from`; + } + let where = ''; + if (options.index !== null) { + where += ` ${table}.id=${options.index}`; + } + if (options.end) { + where += `${where ? ' AND' : ''} ${table}.ts < ${options.end}`; + } + if (options.start) { + where += `${where ? ' AND' : ''} ${table}.ts >= ${options.start}`; + //add last value before start + let subQuery; + let subWhere; + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${options.from ? ', sources.name as from' : ''}${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + subQuery += ` INNER JOIN sources ON sources.id=${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${table}.id=${options.index}`; + } + if (options.ignoreNull) { + //subWhere += (subWhere ? " AND" : '') + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} ${table}.ts < ${options.start}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${table}.ts DESC LIMIT 1`; + where += ` UNION ALL (${subQuery})`; + //add next value after end + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${options.from ? ', sources.name as from' : ''}${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + subQuery += ` INNER JOIN sources ON sources.id=${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${table}.id=${options.index}`; + } + if (options.ignoreNull) { + //subWhere += (subWhere ? " AND" : '') + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} ${table}.ts >= ${options.end}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${table}.ts ASC LIMIT 1`; + where += ` UNION ALL(${subQuery})`; + } + if (where) { + query += ` WHERE ${where}`; + } + query += ' ORDER BY ts'; + if ((!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries)) { + query += ' DESC'; + } + else { + query += ' ASC'; + } + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + query += ` LIMIT ${options.count + 2}`; + } + query += ';'; + return query; +} +function deleteFromTable(_dbName, table, index, start, end) { + let query = `DELETE FROM ${table} WHERE`; + query += ` id=${index}`; + if (start && end) { + query += ` AND ts>=${start} AND ts <= ${end}`; + } + else if (start) { + query += ` AND ts=${start}`; + } + query += ';'; + return query; +} +function update(_dbName, index, state, from, table) { + if (!state || state.val === null || state.val === undefined) { + state.val = 'NULL'; + } + else if (table === 'ts_string') { + state.val = `'${state.val.toString().replace(/'/g, '')}'`; + } + let query = `UPDATE ${table} SET `; + const vals = []; + if (state.val !== undefined) { + vals.push(`val=${state.val}`); + } + if (state.q !== undefined) { + vals.push(`q=${state.q}`); + } + if (from !== undefined) { + vals.push(`_from=${from}`); + } + if (state.ack !== undefined) { + vals.push(`ack=${!!state.ack}`); + } + query += vals.join(', '); + query += ' WHERE '; + query += ` id=${index}`; + query += ` AND ts=${state.ts}`; + query += ';'; + return query; +} +//# sourceMappingURL=postgresql.js.map \ No newline at end of file diff --git a/build/lib/postgresql.js.map b/build/lib/postgresql.js.map new file mode 100644 index 00000000..4ae476e9 --- /dev/null +++ b/build/lib/postgresql.js.map @@ -0,0 +1 @@ +{"version":3,"file":"postgresql.js","sourceRoot":"","sources":["../../src/lib/postgresql.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/lib/sql-client-pool.js b/build/lib/sql-client-pool.js new file mode 100644 index 00000000..a4e13f0f --- /dev/null +++ b/build/lib/sql-client-pool.js @@ -0,0 +1,367 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +var _a; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SQLClientPool = void 0; +const sql_client_1 = __importDefault(require("./sql-client")); +class SQLClientPool { + static MESSAGES = { + POOL_NOT_OPEN: "The pool is not open; please call 'open' before invoking this method.", + TOO_MANY_RETURNED: 'More clients have been returned to the pool than were active. A client may have been returned twice.', + EXHAUSTED: 'The maximum number of clients are already active; cannot obtain a new client.', + MAX_WAIT: 'The maximum number of clients are already active and the maximum wait time has been exceeded; cannot obtain a new client.', + INVALID: 'Unable to create a valid client.', + INTERNAL_ERROR: 'Internal error.', + INVALID_ARGUMENT: 'Invalid argument.', + NULL_RETURNED: 'A null object was returned.', + CLOSED_WITH_ACTIVE: 'The pool was closed, but some clients remain active (were never returned).', + }; + static DEFAULT_WAIT_INTERVAL = 50; + static DEFAULT_RETRY_INTERVAL = 50; + pool = []; + poolOptions = {}; + poolIsOpen = false; + returned = 0; + active = 0; + evictionRunner = null; + sqlOptions; + factory; + constructor(poolOptions, sqlOptions, factory) { + this.factory = factory; + // CONFIGURATION OPTIONS: + // - `min_idle` - minimum number of idle connections in an "empty" pool + // - `max_idle` - maximum number of idle connections in a "full" pool + // - `max_active` - maximum number of connections active at one time + // - `when_exhausted` - what to do when max_active is reached (`grow`,`block`,`fail`), + // - `max_wait` - when `when_exhausted` is `block` max time (in millis) to wait before failure, use < 0 for no maximum + // - `wait_interval` - when `when_exhausted` is `BLOCK`, amount of time (in millis) to wait before rechecking if connections are available + // - `max_retries` - number of times to attempt to create another new connection when a newly created connection is invalid; when `null` no retry attempts will be made; when < 0 an infinite number of retries will be attempted + // - `retry_interval` - when `max_retries` is > 0, amount of time (in millis) to wait before retrying + // - `max_age` - when a positive integer, connections that have been idle for `max_age` milliseconds will be considered invalid and eligable for eviction + // - `evictionRunInterval` - when a positive integer, the number of milliseconds between eviction runs; during an eviction run idle connections will be tested for validity and if invalid, evicted from the pool + // - `eviction_run_length` - when a positive integer, the maxiumum number of connections to examine per eviction run (when not set, all idle connections will be evamined during each eviction run) + // - `unref_eviction_runner` - unless `false`, `unref` (https://nodejs.org/api/timers.html#timers_unref) will be called on the eviction run interval timer + this.sqlOptions = sqlOptions; + this.factory = factory; + this.open(poolOptions); + } + create(callback) { + const client = new sql_client_1.default(this.sqlOptions, this.factory); + client.connect(err => callback(err, client)); + } + activate(client, callback) { + callback(null, client); + } + validate(client, callback) { + if (client && + (!client.pooled_at || + !this.poolOptions?.max_age || + Date.now() - client.pooled_at < this.poolOptions.max_age)) { + callback(null, true, client); + } + else { + callback(null, false, client); + } + } + passivate(client, callback) { + return callback(null, client); + } + destroy(client, callback) { + if (client) { + client.disconnect(callback); + } + else { + callback?.(); + } + } + open(opts, callback) { + this._config(opts, err => { + this.poolIsOpen = true; + callback?.(err); + }); + } + close(callback) { + this.poolIsOpen = false; + if (this.evictionRunner) { + this.evictionRunner.ref(); + clearTimeout(this.evictionRunner); + this.evictionRunner = null; + } + if (this.pool.length > 0) { + this.destroy(this.pool.shift(), () => this.close(callback)); + return; + } + if (this.active > 0) { + callback?.(new Error(_a.MESSAGES.CLOSED_WITH_ACTIVE)); + return; + } + callback?.(); + } + execute(sql, callback) { + this.borrow((err, client) => { + if (err) { + callback(err); + return; + } + if (client == null) { + callback(new Error('non-null client expected')); + return; + } + client.execute(sql, (err, result) => this.return(client, () => callback(err, result))); + }); + } + borrow(callback, blockedSince = null, retryCount = 0) { + if (typeof callback !== 'function') { + throw new Error(_a.MESSAGES.INVALID_ARGUMENT); + } + if (!this.poolIsOpen) { + return callback(new Error(_a.MESSAGES.POOL_NOT_OPEN)); + } + if (this.poolOptions.max_active && + this.active >= this.poolOptions.max_active && + this.poolOptions.when_exhausted === 'fail') { + return callback(new Error(_a.MESSAGES.EXHAUSTED)); + } + if (this.poolOptions.max_active && + this.active >= this.poolOptions.max_active && + this.poolOptions.when_exhausted === 'block') { + if (blockedSince && Date.now() - blockedSince >= this.poolOptions.max_wait) { + return callback(new Error(_a.MESSAGES.MAX_WAIT)); + } + blockedSince ||= Date.now(); + setTimeout(() => { + this.borrow(callback, blockedSince, retryCount); + }, this.poolOptions.wait_interval); + return; + } + if (this.pool.length > 0) { + const client = this.pool.shift(); + this.#activateAndValidateOrDestroy(client, (err, valid, client) => { + if (err) { + callback(err); + return; + } + if (!valid || !client) { + this.borrow(callback); + return; + } + client.pooled_at = null; + client.borrowed_at = Date.now(); + this.active++; + return callback(null, client); + }); + return; + } + return this.create((err, client) => { + if (err) { + return callback(err); + } + return this.#activateAndValidateOrDestroy(client, (err, valid, client) => { + if (err) { + return callback(err); + } + if (!valid || !client) { + if (this.poolOptions.max_retries && this.poolOptions.max_retries > retryCount) { + return setTimeout(() => { + return this.borrow(callback, blockedSince, retryCount + 1); + }, this.poolOptions.retry_interval || 0); + } + return callback(new Error(_a.MESSAGES.INVALID)); + } + client.pooled_at = null; + client.borrowed_at = Date.now(); + this.active++; + return callback(null, client); + }); + }); + } + return(client, callback) { + if (!client) { + callback?.(new Error(_a.MESSAGES.NULL_RETURNED)); + return; + } + if (this.active <= 0) { + callback?.(new Error(_a.MESSAGES.TOO_MANY_RETURNED)); + return; + } + this.returned++; + this.active--; + this.passivate(client, (err, client) => { + if (err || !client) { + callback?.(err || new Error(_a.MESSAGES.NULL_RETURNED)); + return; + } + client.pooled_at = Date.now(); + client.borrowed_at = null; + if (this.pool.length >= this.poolOptions.max_idle) { + this.destroy(client, callback); + return; + } + this.pool.push(client); + callback?.(); + }); + return; + } + _config(opts, callback) { + if (opts.max_retries !== null && + opts.max_retries !== undefined && + (typeof opts.max_retries !== 'number' || opts.max_retries <= 0)) { + opts.max_retries = null; + } + if (opts.max_retries) { + if (opts.retry_interval && (typeof opts.retry_interval !== 'number' || opts.retry_interval <= 0)) { + opts.retry_interval = 0; + } + else if (opts.retry_interval == null) { + opts.retry_interval = _a.DEFAULT_RETRY_INTERVAL; + } + } + if (typeof opts.max_idle === 'number' && opts.max_idle < 0) { + opts.max_idle = Number.MAX_VALUE; + } + else if (typeof opts.max_idle !== 'number') { + opts.max_idle = 0; + } + if (typeof opts.min_idle !== 'number' || opts.min_idle < 0) { + opts.min_idle = 0; + } + if (opts.min_idle > opts.max_idle) { + opts.min_idle = opts.max_idle; + } + if (typeof opts.max_active !== 'number' || opts.max_active < 0) { + opts.max_active = Number.MAX_VALUE; + } + if (typeof opts.max_wait !== 'number' || opts.max_wait < 0) { + opts.max_wait = Number.MAX_VALUE; + } + if (typeof opts.wait_interval !== 'number' || opts.wait_interval < 0) { + opts.wait_interval = _a.DEFAULT_WAIT_INTERVAL; + } + if (opts.when_exhausted !== 'grow' && opts.when_exhausted !== 'block' && opts.when_exhausted !== 'fail') { + opts.when_exhausted = 'grow'; + } + if (opts.max_age === null || opts.max_age === undefined || opts.max_age < 0) { + opts.max_age = Number.MAX_VALUE; + } + this.poolOptions = opts; + this.#reconfig(callback); + } + #reconfig(callback) { + if (this.evictionRunner) { + this.evictionRunner.ref(); + clearTimeout(this.evictionRunner); + this.evictionRunner = null; + } + this._evict(err => { + if (err) { + callback?.(err); + return; + } + this._prepopulate((err, borrowed) => { + if (this.poolOptions.evictionRunInterval && this.poolOptions.evictionRunInterval > 0) { + this.evictionRunner = setInterval(() => this.#evictionRun(), this.poolOptions.evictionRunInterval); + if (this.poolOptions.unref_eviction_runner !== false) { + this.evictionRunner.unref(); + } + } + callback?.(err, borrowed); + }); + }); + } + _evict(callback) { + return this.#evictionRun(0, callback); + } + #evictionRun(numToCheck, callback) { + const newPool = []; + let numChecked = 0; + while (this.pool.length > 0) { + const client = this.pool.shift(); + if (numToCheck === null || numToCheck === undefined || numToCheck <= 0 || numChecked < numToCheck) { + numChecked += 1; + if (newPool.length < this.poolOptions.max_idle && client) { + newPool.push(client); + } + else { + client.disconnect(); + } + } + else { + newPool.push(client); + } + } + this.pool = newPool; + callback?.(); + } + _prepopulate(callback) { + const n = this.poolOptions.min_idle - this.pool.length; + if (n > 0) { + this.#borrowN(n, [], (err, borrowed) => { + if (err) { + return callback(err); + } + this.#returnN(borrowed, callback); + }); + return; + } + return callback(); + } + #borrowN(n, borrowed, callback) { + if (n > borrowed.length) { + this.borrow((err, client) => { + if (client) { + borrowed.push(client); + } + if (err) { + this.#returnN(borrowed, () => callback(err)); + return; + } + this.#borrowN(n, borrowed, callback); + }); + return; + } + return callback(null, borrowed); + } + #returnN(borrowed, callback) { + if (!Array.isArray(borrowed)) { + callback(new Error(_a.MESSAGES.INTERNAL_ERROR)); + return; + } + if (borrowed?.length > 0) { + const client = borrowed.shift(); + this.return(client, () => this.#returnN(borrowed, callback)); + return; + } + return callback(null); + } + #activateAndValidateOrDestroy(client, callback) { + return this.activate(client, (err, client) => { + if (err) { + if (client) { + this.destroy(client, () => callback(err, false, null)); + return; + } + callback(err, false, null); + return; + } + this.validate(client, (err, valid, client) => { + if (err) { + if (client) { + this.destroy(client, () => callback(err, false, null)); + return; + } + return callback(err, false, null); + } + if (!valid || !client) { + this.destroy(client, () => callback(null, false, null)); + return; + } + callback(null, true, client); + }); + }); + } +} +exports.SQLClientPool = SQLClientPool; +_a = SQLClientPool; +//# sourceMappingURL=sql-client-pool.js.map \ No newline at end of file diff --git a/build/lib/sql-client-pool.js.map b/build/lib/sql-client-pool.js.map new file mode 100644 index 00000000..3bfd0211 --- /dev/null +++ b/build/lib/sql-client-pool.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sql-client-pool.js","sourceRoot":"","sources":["../../src/lib/sql-client-pool.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/lib/sql-client.js b/build/lib/sql-client.js new file mode 100644 index 00000000..8f1f3462 --- /dev/null +++ b/build/lib/sql-client.js @@ -0,0 +1,110 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const node_events_1 = require("node:events"); +class SQLClient extends node_events_1.EventEmitter { + options; + factory; + pooled_at = null; + borrowed_at = null; + connected_at = null; + connection; + constructor(options, connectionFactory) { + super(); + this.options = options; + this.factory = connectionFactory; + } + connect(callback) { + if (!this.connection) { + return this.factory.openConnection(this.options, (err, connection) => { + if (err) { + callback?.(err); + return; + } + this.connection = connection; + this.connected_at = Date.now(); + callback?.(); + }); + } + callback?.(); + } + connectAsync() { + if (!this.connection) { + return new Promise((resolve, reject) => this.factory.openConnection(this.options, (err, connection) => { + if (err) { + reject(err); + } + else { + this.connection = connection; + this.connected_at = Date.now(); + resolve(); + } + })); + } + return Promise.resolve(); + } + disconnect(callback) { + if (this.connection) { + this.factory.closeConnection(this.connection, err => { + if (err) { + callback?.(err); + return; + } + this.connection = null; + this.connected_at = null; + callback?.(); + }); + return; + } + return callback?.(); + } + disconnectAsync() { + if (this.connection) { + return new Promise((resolve, reject) => this.factory.closeConnection(this.connection, err => { + if (err) { + reject(err); + } + else { + this.connection = null; + this.connected_at = null; + resolve(); + } + })); + } + return Promise.resolve(); + } + execute(sql, callback) { + if (!this.connection) { + return this.connect(err => { + if (err) { + callback(err); + } + else { + this.execute(sql, callback); + } + }); + } + this.factory.execute(this.connection, sql, (err, result) => { + if (err) { + callback(err); + } + else { + callback(null, result); + } + }); + } + async executeAsync(sql) { + if (!this.connection) { + await this.connectAsync(); + } + return new Promise((resolve, reject) => this.factory.execute(this.connection, sql, (err, result) => { + if (err) { + reject(err); + } + else { + resolve(result); + } + })); + } +} +exports.default = SQLClient; +//# sourceMappingURL=sql-client.js.map \ No newline at end of file diff --git a/build/lib/sql-client.js.map b/build/lib/sql-client.js.map new file mode 100644 index 00000000..ab18a818 --- /dev/null +++ b/build/lib/sql-client.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sql-client.js","sourceRoot":"","sources":["../../src/lib/sql-client.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/lib/sqlite.js b/build/lib/sqlite.js new file mode 100644 index 00000000..bdacfa40 --- /dev/null +++ b/build/lib/sqlite.js @@ -0,0 +1,239 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.init = init; +exports.destroy = destroy; +exports.getFirstTs = getFirstTs; +exports.insert = insert; +exports.retention = retention; +exports.getIdSelect = getIdSelect; +exports.getIdInsert = getIdInsert; +exports.getIdUpdate = getIdUpdate; +exports.getFromSelect = getFromSelect; +exports.getFromInsert = getFromInsert; +exports.getCounterDiff = getCounterDiff; +exports.getHistory = getHistory; +exports.deleteFromTable = deleteFromTable; +exports.update = update; +function init(_dbName) { + return [ + 'CREATE TABLE sources (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT);', + 'CREATE TABLE datapoints (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT, type INTEGER);', + 'CREATE TABLE ts_number (id INTEGER, ts INTEGER, val REAL, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_string (id INTEGER, ts INTEGER, val TEXT, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_bool (id INTEGER, ts INTEGER, val BOOLEAN, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_counter (id INTEGER, ts INTEGER, val REAL, PRIMARY KEY(id, ts));', + ]; +} +function destroy(_dbName) { + return [ + 'DROP TABLE ts_counter;', + 'DROP TABLE ts_number;', + 'DROP TABLE ts_string;', + 'DROP TABLE ts_bool;', + 'DROP TABLE sources;', + 'DROP TABLE datapoints;', + ]; +} +function getFirstTs(_dbName, table) { + return `SELECT id, MIN(ts) AS ts FROM ${table} GROUP BY id;`; +} +function insert(_dbName, index, values) { + const insertValues = {}; + values.forEach(value => { + // state, from, db + insertValues[value.table] ||= []; + if (!value.state || value.state.val === null || value.state.val === undefined) { + value.state.val = 'NULL'; + } + else if (value.table === 'ts_bool') { + value.state.val = value.state.val ? 1 : 0; + } + else if (value.table === 'ts_string') { + value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; + } + else if (value.table === 'ts_number') { + if (isNaN(value.state.val)) { + value.state.val = 'NULL'; + } + } + if (value.table === 'ts_counter') { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val})`); + } + else { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val}, ${value.state.ack ? 1 : 0}, ${value.from || 0}, ${value.state.q || 0})`); + } + }); + const query = []; + for (const table in insertValues) { + if (table === 'ts_counter') { + while (insertValues[table].length) { + query.push(`INSERT INTO ts_counter (id, ts, val) VALUES ${insertValues[table].splice(0, 500).join(',')};`); + } + } + else { + while (insertValues[table].length) { + query.push(`INSERT INTO ${table} (id, ts, val, ack, _from, q) VALUES ${insertValues[table].splice(0, 500).join(',')};`); + } + } + } + return query.join(' '); +} +function retention(_dbName, index, table, retention) { + const d = new Date(); + d.setSeconds(-retention); + let query = `DELETE FROM ${table} WHERE`; + query += ` id=${index}`; + query += ` AND ts < ${d.getTime()}`; + query += ';'; + return query; +} +function getIdSelect(_dbName, name) { + if (!name) { + return 'SELECT id, type, name FROM datapoints;'; + } + return `SELECT id, type, name FROM datapoints WHERE name='${name}';`; +} +function getIdInsert(_dbName, name, type) { + return `INSERT INTO datapoints (name, type) VALUES('${name}', ${type});`; +} +function getIdUpdate(_dbName, id, type) { + return `UPDATE datapoints SET type = ${type} WHERE id = ${id};`; +} +function getFromSelect(_dbName, name) { + if (!name) { + return 'SELECT id, name FROM sources;'; + } + return `SELECT id FROM sources WHERE name='${name}';`; +} +function getFromInsert(_dbName, values) { + return `INSERT INTO sources (name) VALUES('${values}');`; +} +function getCounterDiff(_dbName, options) { + // Take first real value after start + const subQueryStart = `SELECT ts, val FROM ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC LIMIT 1`; + // Take last real value before end + const subQueryEnd = `SELECT ts, val FROM ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts DESC LIMIT 1`; + // Take last value before start + const subQueryFirst = `SELECT ts, val FROM ts_number WHERE id=${options.index} AND ts< ${options.start} ORDER BY ts DESC LIMIT 1`; + // Take next value after end + const subQueryLast = `SELECT ts, val FROM ts_number WHERE id=${options.index} AND ts>= ${options.end} ORDER BY ts ASC LIMIT 1`; + // get values from counters where counter changed from up to down (e.g. counter changed) + const subQueryCounterChanges = `SELECT ts, val FROM ts_counter WHERE id=${options.index} AND ts>${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC`; + return (`SELECT DISTINCT(a.ts), a.val from ((${subQueryFirst})\n` + + `UNION ALL \n(${subQueryStart})\n` + + `UNION ALL \n(${subQueryEnd})\n` + + `UNION ALL \n(${subQueryLast})\n` + + `UNION ALL \n(${subQueryCounterChanges})\n` + + `ORDER BY ts) a;`); +} +function getHistory(_dbName, table, options) { + let query = `SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${options.from ? `, sources.name as 'from'` : ''}${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + query += ` INNER JOIN sources ON sources.id=${table}._from`; + } + let where = ''; + if (options.index !== null) { + where += ` ${table}.id=${options.index}`; + } + if (options.end) { + where += `${where ? ' AND' : ''} ${table}.ts < ${options.end}`; + } + if (options.start) { + where += `${where ? ' AND' : ''} ${table}.ts >= ${options.start}`; + // add last value before start + let subQuery; + let subWhere; + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${options.from ? `, sources.name as 'from'` : ''}${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + subQuery += ` INNER JOIN sources ON sources.id=${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${table}.id=${options.index}`; + } + if (options.ignoreNull) { + // subWhere += (subWhere ? " AND" : '') + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} ${table}.ts < ${options.start}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${table}.ts DESC LIMIT 1`; + where += ` UNION ALL SELECT * from (${subQuery})`; + // add next value after end + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${options.from ? `, sources.name as 'from'` : ''}${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + subQuery += ` INNER JOIN sources ON sources.id=${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${table}.id=${options.index}`; + } + if (options.ignoreNull) { + // subWhere += (subWhere ? " AND" : '') + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} ${table}.ts >= ${options.end}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${table}.ts ASC LIMIT 1`; + where += ` UNION ALL SELECT * from (${subQuery}) `; + } + if (where) { + query += ` WHERE ${where}`; + } + query += ' ORDER BY ts'; + if ((!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries)) { + query += ' DESC'; + } + else { + query += ' ASC'; + } + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + query += ` LIMIT ${options.count + 2}`; + } + query += ';'; + return query; +} +function deleteFromTable(_dbName, table, index, start, end) { + let query = `DELETE FROM ${table} WHERE`; + query += ` id=${index}`; + if (start && end) { + query += ` AND ts>=${start} AND ts <= ${end}`; + } + else if (start) { + query += ` AND ts=${start}`; + } + query += ';'; + return query; +} +function update(_dbName, index, state, from, table) { + if (!state || state.val === null || state.val === undefined) { + state.val = 'NULL'; + } + else if (table === 'ts_string') { + state.val = `'${state.val.toString().replace(/'/g, '')}'`; + } + let query = `UPDATE ${table} SET `; + const vals = []; + if (state.val !== undefined) { + vals.push(`val=${state.val}`); + } + if (state.q !== undefined) { + vals.push(`q=${state.q}`); + } + if (from !== undefined) { + vals.push(`_from=${from}`); + } + if (state.ack !== undefined) { + vals.push(`ack=${state.ack ? 1 : 0}`); + } + query += vals.join(', '); + query += ' WHERE '; + query += ` id=${index}`; + query += ` AND ts=${state.ts}`; + query += ';'; + return query; +} +//# sourceMappingURL=sqlite.js.map \ No newline at end of file diff --git a/build/lib/sqlite.js.map b/build/lib/sqlite.js.map new file mode 100644 index 00000000..69166951 --- /dev/null +++ b/build/lib/sqlite.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/lib/sqlite.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/lib/sqlite3-client.js b/build/lib/sqlite3-client.js new file mode 100644 index 00000000..b87223db --- /dev/null +++ b/build/lib/sqlite3-client.js @@ -0,0 +1,65 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SQLite3ClientPool = exports.SQLite3Client = exports.SQLite3ConnectionFactory = void 0; +const connection_factory_1 = require("./connection-factory"); +const sql_client_1 = __importDefault(require("./sql-client")); +const sql_client_pool_1 = require("./sql-client-pool"); +class SQLite3ConnectionFactory extends connection_factory_1.ConnectionFactory { + Database; + openConnection(options, callback) { + if (!this.Database) { + void import('sqlite3').then(sqlite3 => { + this.Database = sqlite3.default.Database; + this.openConnection(options, callback); + }); + return; + } + if (options.mode) { + const db = new this.Database(options.fileName, options.mode, (err) => { + if (err) { + callback(err); + } + else { + callback(null, db); + } + }); + return; + } + const db = new this.Database(options.fileName, (err) => { + if (err) { + callback(err); + } + else { + callback(null, db); + } + }); + } + closeConnection(db, callback) { + if (db) { + db.close(callback); + } + else { + callback?.(); + } + } + execute(db, sql, callback) { + db.all(sql, [], callback); + } +} +exports.SQLite3ConnectionFactory = SQLite3ConnectionFactory; +class SQLite3Client extends sql_client_1.default { + constructor(sqliteOptions) { + super(sqliteOptions, new SQLite3ConnectionFactory()); + } +} +exports.SQLite3Client = SQLite3Client; +class SQLite3ClientPool extends sql_client_pool_1.SQLClientPool { + constructor(poolOptions, sqliteOptions) { + super(poolOptions, sqliteOptions, new SQLite3ConnectionFactory()); + } +} +exports.SQLite3ClientPool = SQLite3ClientPool; +//# sourceMappingURL=sqlite3-client.js.map \ No newline at end of file diff --git a/build/lib/sqlite3-client.js.map b/build/lib/sqlite3-client.js.map new file mode 100644 index 00000000..6826d599 --- /dev/null +++ b/build/lib/sqlite3-client.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sqlite3-client.js","sourceRoot":"","sources":["../../src/lib/sqlite3-client.ts"],"names":[],"mappings":";;;;;;AAAA,6DAAyD;AACzD,8DAAqC;AACrC,uDAAmE;AAQnE,MAAa,wBAAyB,SAAQ,sCAAiB;IACnD,QAAQ,CAA8B;IAE9C,cAAc,CAAC,OAAuB,EAAE,QAA4D;QAChG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,KAAK,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;gBAClC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;gBACzC,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC;YACH,OAAO;QACX,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,GAAiB,EAAQ,EAAE;gBACrF,IAAI,GAAG,EAAE,CAAC;oBACN,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAClB,CAAC;qBAAM,CAAC;oBACJ,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACvB,CAAC;YACL,CAAC,CAAC,CAAC;YACH,OAAO;QACX,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAiB,EAAQ,EAAE;YACvE,IAAI,GAAG,EAAE,CAAC;gBACN,QAAQ,CAAC,GAAG,CAAC,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACJ,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACvB,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;IAED,eAAe,CAAC,EAAY,EAAE,QAAuC;QACjE,IAAI,EAAE,EAAE,CAAC;YACL,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;aAAM,CAAC;YACJ,QAAQ,EAAE,EAAE,CAAC;QACjB,CAAC;IACL,CAAC;IAED,OAAO,CAAI,EAAY,EAAE,GAAW,EAAE,QAAwD;QAC1F,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC9B,CAAC;CACJ;AA1CD,4DA0CC;AAED,MAAa,aAAc,SAAQ,oBAAS;IACxC,YAAY,aAA6B;QACrC,KAAK,CAAC,aAAa,EAAE,IAAI,wBAAwB,EAAE,CAAC,CAAC;IACzD,CAAC;CACJ;AAJD,sCAIC;AAED,MAAa,iBAAkB,SAAQ,+BAAa;IAChD,YAAY,WAAuB,EAAE,aAA6B;QAC9D,KAAK,CAAC,WAAW,EAAE,aAAa,EAAE,IAAI,wBAAwB,EAAE,CAAC,CAAC;IACtE,CAAC;CACJ;AAJD,8CAIC"} \ No newline at end of file diff --git a/build/main.js b/build/main.js new file mode 100644 index 00000000..6c24aabb --- /dev/null +++ b/build/main.js @@ -0,0 +1,3646 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SqlAdapter = void 0; +const adapter_core_1 = require("@iobroker/adapter-core"); // Get common adapter utils +const aggregate_1 = require("./lib/aggregate"); +const node_fs_1 = require("node:fs"); +const node_path_1 = require("node:path"); +const plugin_docker_1 = require("@iobroker/plugin-docker"); +const MSSQL = __importStar(require("./lib/mssql")); +const MySQL = __importStar(require("./lib/mysql")); +const PostgreSQL = __importStar(require("./lib/postgresql")); +const SQLite = __importStar(require("./lib/sqlite")); +const mssql_client_1 = require("./lib/mssql-client"); +const mysql_client_1 = require("./lib/mysql-client"); +const postgresql_client_1 = require("./lib/postgresql-client"); +const sqlite3_client_1 = require("./lib/sqlite3-client"); +const SQLFuncs = { + mssql: { + init: MSSQL.init, + destroy: MSSQL.destroy, + getFirstTs: MSSQL.getFirstTs, + insert: MSSQL.insert, + retention: MSSQL.retention, + getIdSelect: MSSQL.getIdSelect, + getIdInsert: MSSQL.getIdInsert, + getIdUpdate: MSSQL.getIdUpdate, + getFromSelect: MSSQL.getFromSelect, + getFromInsert: MSSQL.getFromInsert, + getCounterDiff: MSSQL.getCounterDiff, + getHistory: MSSQL.getHistory, + deleteFromTable: MSSQL.deleteFromTable, + update: MSSQL.update, + }, + mysql: { + init: MySQL.init, + destroy: MySQL.destroy, + getFirstTs: MySQL.getFirstTs, + insert: MySQL.insert, + retention: MySQL.retention, + getIdSelect: MySQL.getIdSelect, + getIdInsert: MySQL.getIdInsert, + getIdUpdate: MySQL.getIdUpdate, + getFromSelect: MySQL.getFromSelect, + getFromInsert: MySQL.getFromInsert, + getCounterDiff: MySQL.getCounterDiff, + getHistory: MySQL.getHistory, + deleteFromTable: MySQL.deleteFromTable, + update: MySQL.update, + }, + postgresql: { + init: PostgreSQL.init, + destroy: PostgreSQL.destroy, + getFirstTs: PostgreSQL.getFirstTs, + insert: PostgreSQL.insert, + retention: PostgreSQL.retention, + getIdSelect: PostgreSQL.getIdSelect, + getIdInsert: PostgreSQL.getIdInsert, + getIdUpdate: PostgreSQL.getIdUpdate, + getFromSelect: PostgreSQL.getFromSelect, + getFromInsert: PostgreSQL.getFromInsert, + getCounterDiff: PostgreSQL.getCounterDiff, + getHistory: PostgreSQL.getHistory, + deleteFromTable: PostgreSQL.deleteFromTable, + update: PostgreSQL.update, + }, + sqlite: { + init: SQLite.init, + destroy: SQLite.destroy, + getFirstTs: SQLite.getFirstTs, + insert: SQLite.insert, + retention: SQLite.retention, + getIdSelect: SQLite.getIdSelect, + getIdInsert: SQLite.getIdInsert, + getIdUpdate: SQLite.getIdUpdate, + getFromSelect: SQLite.getFromSelect, + getFromInsert: SQLite.getFromInsert, + getCounterDiff: SQLite.getCounterDiff, + getHistory: SQLite.getHistory, + deleteFromTable: SQLite.deleteFromTable, + update: SQLite.update, + }, +}; +const clients = { + postgresql: { multiRequests: true }, + mysql: { multiRequests: true }, + sqlite: { multiRequests: false }, + mssql: { multiRequests: true }, +}; +const types = { + number: 0, + string: 1, + boolean: 2, + object: 1, +}; +const dbNames = ['ts_number', 'ts_string', 'ts_bool']; +const storageTypes = ['Number', 'String', 'Boolean']; +function isEqual(a, b) { + //console.log('Compare ' + JSON.stringify(a) + ' with ' + JSON.stringify(b)); + // Create arrays of property names + if (a === null || a === undefined || b === null || b === undefined) { + return a === b; + } + const aProps = Object.getOwnPropertyNames(a); + const bProps = Object.getOwnPropertyNames(b); + // If the number of properties is different, + // objects are not equivalent + if (aProps.length !== bProps.length) { + //console.log('num props different: ' + JSON.stringify(aProps) + ' / ' + JSON.stringify(bProps)); + return false; + } + for (let i = 0; i < aProps.length; i++) { + const propName = aProps[i]; + if (typeof a[propName] !== typeof b[propName]) { + //console.log('type props ' + propName + ' different'); + return false; + } + else if (typeof a[propName] === 'object') { + if (!isEqual(a[propName], b[propName])) { + return false; + } + } + else { + // If values of same property are not equal, + // objects are not equivalent + if (a[propName] !== b[propName]) { + //console.log('props ' + propName + ' different'); + return false; + } + } + } + // If we made it this far, objects + // are considered equivalent + return true; +} +const MAX_TASKS = 100; +function sortByTs(a, b) { + const aTs = a.ts; + const bTs = b.ts; + return aTs < bTs ? -1 : aTs > bTs ? 1 : 0; +} +class SqlAdapter extends adapter_core_1.Adapter { + sqlDPs = {}; + from = {}; + tasks = []; + tasksReadType = []; + tasksStart = []; + isFromRunning = {}; + aliasMap = {}; + finished = false; + sqlConnected = null; + multiRequests = true; + subscribeAll = false; + clientPool = null; + reconnectTimeout = null; + testConnectTimeout = null; + dpOverviewTimeout = null; + bufferChecker = null; + activeConnections = 0; + poolBorrowGuard = []; // required until https://github.com/intellinote/sql-client/issues/7 is fixed + logConnectionUsage = true; + postgresDbCreated = false; + lockTasks = false; + sqlFuncs = null; + constructor(options = {}) { + super({ + ...options, + name: 'sql', + ready: () => this.main(), + message: (obj) => this.processMessage(obj), + stateChange: (id, state) => { + id = this.aliasMap[id] || id; + this.pushHistory(id, state); + }, + objectChange: (id, obj) => { + let tmpState; + const now = Date.now(); + const formerAliasId = this.aliasMap[id] || id; + if (obj?.common?.custom?.[this.namespace] && + typeof obj.common.custom[this.namespace] === 'object' && + obj.common.custom[this.namespace].enabled) { + const realId = id; + let checkForRemove = true; + if (obj.common.custom?.[this.namespace]?.aliasId) { + if (obj.common.custom[this.namespace].aliasId !== id) { + this.aliasMap[id] = obj.common.custom[this.namespace].aliasId; + this.log.debug(`Registered Alias: ${id} --> ${this.aliasMap[id]}`); + id = this.aliasMap[id]; + checkForRemove = false; + } + else { + this.log.warn(`Ignoring Alias-ID because identical to ID for ${id}`); + obj.common.custom[this.namespace].aliasId = ''; + } + } + if (checkForRemove && this.aliasMap[id]) { + this.log.debug(`Removed Alias: ${id} !-> ${this.aliasMap[id]}`); + delete this.aliasMap[id]; + } + if (!this.sqlDPs[formerAliasId]?.config && !this.subscribeAll) { + // un-subscribe + for (const _id in this.sqlDPs) { + if (Object.prototype.hasOwnProperty.call(this.sqlDPs, _id) && + Object.prototype.hasOwnProperty.call(this.sqlDPs, this.sqlDPs[_id].realId)) { + this.unsubscribeForeignStates(this.sqlDPs[_id].realId); + } + } + this.subscribeAll = true; + this.subscribeForeignStates('*'); + } + if (this.sqlDPs[id] && this.sqlDPs[id].index === undefined) { + this.getId(id, this.sqlDPs[id].dbType, () => this.reInit(id, realId, formerAliasId, obj)); + } + else { + this.reInit(id, realId, formerAliasId, obj); + } + } + else { + if (this.aliasMap[id]) { + this.log.debug(`Removed Alias: ${id} !-> ${this.aliasMap[id]}`); + delete this.aliasMap[id]; + } + const sqlDP = this.sqlDPs[id]; + id = formerAliasId; + if (sqlDP?.config) { + this.log.info(`disabled logging of ${id}`); + if (sqlDP.relogTimeout) { + clearTimeout(sqlDP.relogTimeout); + sqlDP.relogTimeout = null; + } + if (sqlDP.timeout) { + clearTimeout(sqlDP.timeout); + sqlDP.timeout = null; + } + if (sqlDP.state) { + tmpState = { ...sqlDP.state }; + } + const state = sqlDP.state ? tmpState || null : null; + if (sqlDP.config) { + sqlDP.config.enabled = false; + if (sqlDP.skipped && !sqlDP.config.disableSkippedValueLogging) { + this.pushValueIntoDB(id, sqlDP.skipped, false, true); + sqlDP.skipped = null; + } + if (this.config.writeNulls) { + const nullValue = { + val: null, + ts: now, + lc: now, + q: 0x40, + from: `system.adapter.${this.namespace}`, + ack: true, + }; + if (sqlDP.config.changesOnly && state && state.val !== null) { + ((_id, _state, _nullValue) => { + _state.ts = now; + _state.from = `system.adapter.${this.namespace}`; + nullValue.ts += 4; + nullValue.lc += 4; // because of MS SQL + this.log.debug(`Write 1/2 "${_state.val}" _id: ${_id}`); + this.pushValueIntoDB(_id, _state, false, true, () => { + // terminate values with null to indicate adapter stop. timestamp + 1 + this.log.debug(`Write 2/2 "null" _id: ${_id}`); + this.pushValueIntoDB(_id, _nullValue, false, false, () => delete this.sqlDPs[id]); + }); + })(id, state, nullValue); + } + else { + // terminate values with null to indicate adapter stop. timestamp + 1 + this.log.debug(`Write 0 NULL _id: ${id}`); + this.pushValueIntoDB(id, nullValue, false, false, () => delete this.sqlDPs[id]); + } + } + else { + this.storeCached(id, () => delete this.sqlDPs[id]); + } + } + else { + delete this.sqlDPs[id]; + } + } + } + }, + unload: (callback) => { + void this.finish(callback); + }, + }); + } + borrowClientFromPool(callback) { + if (!this.clientPool) { + this.setConnected(false); + return callback(new Error('No database connection')); + } + this.setConnected(true); + if (this.activeConnections >= this.config.maxConnections) { + if (this.logConnectionUsage) { + this.log.debug(`Borrow connection not possible: ${this.activeConnections} >= Max - Store for Later`); + } + this.poolBorrowGuard.push(callback); + return; + } + this.activeConnections++; + if (this.logConnectionUsage) { + this.log.debug(`Borrow connection from pool: ${this.activeConnections} now`); + } + this.clientPool.borrow((err, client) => { + if (!err && client) { + // make sure we always have at least one error listener to prevent crashes + if (client.on && client.listenerCount && !client.listenerCount('error')) { + client.on('error', (err) => this.log.warn(`SQL client error: ${err}`)); + } + } + else if (!client) { + this.activeConnections--; + } + callback(err, client); + }); + } + returnClientToPool(client) { + if (client) { + this.activeConnections--; + } + if (this.logConnectionUsage) { + this.log.debug(`Return connection to pool: ${this.activeConnections} now`); + } + if (this.clientPool && client) { + if (this.poolBorrowGuard.length) { + this.activeConnections++; + if (this.logConnectionUsage) { + this.log.debug(`Borrow returned connection directly: ${this.activeConnections} now`); + } + const callback = this.poolBorrowGuard.shift(); + callback(null, client); + } + else { + try { + this.clientPool.return(client); + } + catch { + // Ignore + } + } + } + } + normalizeCustomConfig(customConfig) { + // maxLength + if (!customConfig.maxLength && customConfig.maxLength !== '0' && customConfig.maxLength !== 0) { + customConfig.maxLength = this.config.maxLength || 0; + } + else { + customConfig.maxLength = parseInt(customConfig.maxLength, 10); + } + // retention + if (customConfig.retention || customConfig.retention === 0) { + customConfig.retention = parseInt(customConfig.retention, 10) || 0; + } + else { + customConfig.retention = this.config.retention; + } + if (customConfig.retention === -1) { + // customRetentionDuration + if (customConfig.customRetentionDuration !== undefined && + customConfig.customRetentionDuration !== null && + customConfig.customRetentionDuration !== '') { + customConfig.customRetentionDuration = + parseInt(customConfig.customRetentionDuration, 10) || 0; + } + else { + customConfig.customRetentionDuration = this.config.customRetentionDuration; + } + customConfig.retention = customConfig.customRetentionDuration * 24 * 60 * 60; + } + // debounceTime and debounce compatibility handling + if (!customConfig.blockTime && customConfig.blockTime !== '0' && customConfig.blockTime !== 0) { + if (!customConfig.debounce && customConfig.debounce !== '0' && customConfig.debounce !== 0) { + customConfig.blockTime = this.config.blockTime || 0; + } + else { + customConfig.blockTime = parseInt(customConfig.debounce, 10) || 0; + } + } + else { + customConfig.blockTime = parseInt(customConfig.blockTime, 10) || 0; + } + if (!customConfig.debounceTime && customConfig.debounceTime !== '0' && customConfig.debounceTime !== 0) { + customConfig.debounceTime = this.config.debounceTime || 0; + } + else { + customConfig.debounceTime = parseInt(customConfig.debounceTime, 10) || 0; + } + // changesOnly + customConfig.changesOnly = customConfig.changesOnly === true || customConfig.changesOnly === 'true'; + // ignoreZero + customConfig.ignoreZero = customConfig.ignoreZero === true || customConfig.ignoreZero === 'true'; + // round + if (customConfig.round !== null && customConfig.round !== undefined && customConfig.round !== '') { + customConfig.round = parseInt(customConfig.round, 10); + if (!isFinite(customConfig.round) || customConfig.round < 0) { + customConfig.round = this.config.round; + } + else { + customConfig.round = Math.pow(10, parseInt(customConfig.round, 10)); + } + } + else { + customConfig.round = this.config.round; + } + // ignoreAboveNumber + if (customConfig.ignoreAboveNumber !== undefined && + customConfig.ignoreAboveNumber !== null && + customConfig.ignoreAboveNumber !== '') { + customConfig.ignoreAboveNumber = parseFloat(customConfig.ignoreAboveNumber) || null; + } + // ignoreBelowNumber incl. ignoreBelowZero compatibility handling + if (customConfig.ignoreBelowNumber !== undefined && + customConfig.ignoreBelowNumber !== null && + customConfig.ignoreBelowNumber !== '') { + customConfig.ignoreBelowNumber = parseFloat(customConfig.ignoreBelowNumber) || null; + } + else if (customConfig.ignoreBelowZero === 'true' || customConfig.ignoreBelowZero === true) { + customConfig.ignoreBelowNumber = 0; + } + // disableSkippedValueLogging + if (customConfig.disableSkippedValueLogging !== undefined && + customConfig.disableSkippedValueLogging !== null && + customConfig.disableSkippedValueLogging !== '') { + customConfig.disableSkippedValueLogging = + customConfig.disableSkippedValueLogging === 'true' || customConfig.disableSkippedValueLogging === true; + } + else { + customConfig.disableSkippedValueLogging = this.config.disableSkippedValueLogging; + } + // enableDebugLogs + if (customConfig.enableDebugLogs !== undefined && + customConfig.enableDebugLogs !== null && + customConfig.enableDebugLogs !== '') { + customConfig.enableDebugLogs = + customConfig.enableDebugLogs === 'true' || customConfig.enableDebugLogs === true; + } + else { + customConfig.enableDebugLogs = this.config.enableDebugLogs; + } + // changesRelogInterval + if (customConfig.changesRelogInterval || customConfig.changesRelogInterval === 0) { + customConfig.changesRelogInterval = parseInt(customConfig.changesRelogInterval, 10) || 0; + } + else { + customConfig.changesRelogInterval = this.config.changesRelogInterval; + } + // changesMinDelta + if (customConfig.changesMinDelta || customConfig.changesMinDelta === 0) { + customConfig.changesMinDelta = parseFloat(customConfig.changesMinDelta.toString().replace(/,/g, '.')) || 0; + } + else { + customConfig.changesMinDelta = this.config.changesMinDelta; + } + // storageType + if (!customConfig.storageType) { + customConfig.storageType = false; + } + // add one day if retention is too small + if (customConfig.retention && customConfig.retention <= 604800) { + customConfig.retention += 86400; + } + return customConfig; + } + reInit(id, realId, formerAliasId, obj) { + const customConfig = this.normalizeCustomConfig(obj.common.custom?.[this.namespace]); + if (this.sqlDPs[formerAliasId]?.config && isEqual(customConfig, this.sqlDPs[formerAliasId].config)) { + if (obj.common.custom?.[this.namespace].enableDebugLogs) { + this.log.debug(`Object ${id} unchanged. Ignore`); + } + return; + } + // relogTimeout + if (this.sqlDPs[formerAliasId] && this.sqlDPs[formerAliasId].relogTimeout) { + clearTimeout(this.sqlDPs[formerAliasId].relogTimeout); + this.sqlDPs[formerAliasId].relogTimeout = null; + } + const writeNull = !this.sqlDPs[id]?.config; + this.sqlDPs[id] = { + config: customConfig, + state: this.sqlDPs[id]?.state || null, + list: this.sqlDPs[id]?.list || [], + inFlight: this.sqlDPs[id]?.inFlight || {}, + timeout: this.sqlDPs[id]?.timeout || null, + ts: this.sqlDPs[id]?.ts || null, + lastLogTime: 0, + relogTimeout: null, + realId: realId, + lastCheck: this.sqlDPs[id]?.lastCheck || Date.now() - Math.floor(Math.random() * 21600000 /* 6 hours */), // randomize lastCheck to avoid all datapoints to be checked at same timepoint + }; + // changesRelogInterval + if (this.sqlDPs[id].config.changesOnly && this.sqlDPs[id].config.changesRelogInterval > 0) { + this.sqlDPs[id].relogTimeout = setTimeout((_id) => this.reLogHelper(_id), this.sqlDPs[id].config.changesRelogInterval * 500 * (1 + Math.random()), id); + } + if (writeNull && this.config.writeNulls) { + this.writeNulls(id); + } + this.log.info(`enabled logging of ${id}, Alias=${id !== realId}, WriteNulls=${writeNull}`); + } + setConnected(isConnected) { + if (this.sqlConnected !== isConnected) { + this.sqlConnected = isConnected; + void this.setState('info.connection', this.sqlConnected, true); + } + } + connect(callback) { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = null; + } + if (!this.clientPool) { + this.setConnected(false); + let sqLiteOptions; + let msSQLOptions; + let mySQLOptions; + let postgreSQLOptions; + const poolOptions = { + max_idle: this.config.dbtype === 'sqlite' ? 1 : 2, + }; + if (this.config.maxConnections) { + poolOptions.max_active = this.config.maxConnections + 1; // we use our own Pool limiter logic, so let library have one more to not block us too early + poolOptions.max_wait = 10000; // hard code for now + poolOptions.when_exhausted = 'block'; + } + if (!this.config.dbtype) { + return this.log.error('DB Type is not defined!'); + } + if (!clients[this.config.dbtype]) { + return this.log.error(`Unknown type "${this.config.dbtype}"`); + } + if (this.config.dbtype === 'postgresql') { + postgreSQLOptions = { + host: this.config.host, + user: this.config.user || '', + password: this.config.password || '', + port: this.config.port || undefined, + database: 'postgres', + ssl: this.config.encrypt + ? { + rejectUnauthorized: !!this.config.rejectUnauthorized, + } + : undefined, + }; + } + else if (this.config.dbtype === 'mssql') { + msSQLOptions = { + server: this.config.host, // needed for MSSQL + user: this.config.user || '', + password: this.config.password || '', + port: this.config.port || undefined, + options: { + encrypt: !!this.config.encrypt, + trustServerCertificate: !this.config.rejectUnauthorized, + }, + }; + } + else if (this.config.dbtype === 'mysql') { + mySQLOptions = { + host: this.config.host, // needed for PostgreSQL , MySQL + user: this.config.user || '', + password: this.config.password || '', + port: this.config.port || undefined, + ssl: this.config.encrypt + ? { + rejectUnauthorized: !!this.config.rejectUnauthorized, + } + : undefined, + }; + } + else if (this.config.dbtype === 'sqlite') { + sqLiteOptions = { fileName: this.getSqlLiteDir(this.config.fileName) }; + } + if (this.config.dbtype === 'postgresql' && !this.postgresDbCreated && postgreSQLOptions) { + // special solution for postgres. Connect first to Db "postgres", create new DB "iobroker" and then connect to "iobroker" DB. + // connect first to DB postgres and create iobroker DB + this.log.info(`Postgres connection options: ${JSON.stringify(postgreSQLOptions).replace(postgreSQLOptions.password || '******', '****')}`); + const _client = new postgresql_client_1.PostgreSQLClient(postgreSQLOptions); + _client.on?.('error', (err) => this.log.warn(`SQL client error: ${err}`)); + return _client.connect((err) => { + if (err) { + this.log.error(err.toString()); + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + } + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + return; + } + if (this.config.doNotCreateDatabase) { + _client.disconnect(); + this.postgresDbCreated = true; + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 100); + } + else { + _client.execute(`CREATE DATABASE ${this.config.dbname};`, (err) => { + _client.disconnect(); + const typedError = err; + if (typedError && typedError.code !== '42P04') { + // if error not about yet exists + this.postgresDbCreated = false; + this.log.error(JSON.stringify(typedError)); + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + } + else { + // remember that DB is created + this.postgresDbCreated = true; + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 100); + } + }); + } + }); + } + if (this.config.dbtype === 'postgresql' && postgreSQLOptions) { + postgreSQLOptions.database = this.config.dbname; + } + try { + if (this.config.dbtype === 'mssql' && msSQLOptions) { + this.clientPool = new mssql_client_1.MSSQLClientPool(poolOptions, msSQLOptions); + } + else if (this.config.dbtype === 'mysql' && mySQLOptions) { + this.clientPool = new mysql_client_1.MySQL2ClientPool(poolOptions, mySQLOptions); + } + else if (this.config.dbtype === 'sqlite' && sqLiteOptions) { + this.clientPool = new sqlite3_client_1.SQLite3ClientPool(poolOptions, sqLiteOptions); + } + else if (this.config.dbtype === 'postgresql' && postgreSQLOptions) { + this.clientPool = new postgresql_client_1.PostgreSQLClientPool(poolOptions, postgreSQLOptions); + } + else { + throw new Error('DB connection options not defined'); + } + return this.clientPool.open(poolOptions, (err) => { + this.activeConnections = 0; + if (err) { + this.clientPool = null; + this.setConnected(false); + this.log.error(JSON.stringify(err)); + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + } + else { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + } + setImmediate(() => this.connect(callback)); + } + }); + } + catch (ex) { + if (ex.toString() === 'TypeError: undefined is not a function') { + this.log.error(`Node.js DB driver for "${this.config.dbtype}" could not be installed.`); + } + else { + this.log.error(ex.toString()); + this.log.error(ex.stack); + } + this.clientPool = null; + this.activeConnections = 0; + this.setConnected(false); + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + return; + } + } + this.allScripts(this.sqlFuncs.init(this.config.dbname, this.config.doNotCreateDatabase), 0, err => { + if (err) { + //this.log.error(err); + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + } + else { + this.log.info(`Connected to ${this.config.dbtype}`); + // read all DB IDs and all FROM ids + this.getAllIds(() => this.getAllFroms(callback)); + } + }); + } + // Find sqlite data directory + getSqlLiteDir(fileName) { + fileName ||= 'sqlite.db'; + fileName = fileName.replace(/\\/g, '/'); + if (fileName[0] === '/' || fileName.match(/^\w:\//)) { + return fileName; + } + // normally /opt/iobroker/node_modules/iobroker.js-controller + // but can be /example/ioBroker.js-controller + const config = (0, node_path_1.join)((0, adapter_core_1.getAbsoluteDefaultDataDir)(), 'sqlite'); + // create sqlite directory + if (!(0, node_fs_1.existsSync)(config)) { + (0, node_fs_1.mkdirSync)(config); + } + return (0, node_path_1.normalize)((0, node_path_1.join)(config, fileName)); + } + async testConnection(msg) { + if (!msg?.message?.config) { + if (msg.callback) { + this.sendTo(msg.from, msg.command, { error: 'invalid config' }, msg.callback); + } + return; + } + let sqLiteOptions; + let msSQLOptions; + let mySQLOptions; + let postgreSQLOptions; + const config = msg.message.config; + config.port = parseInt(config.port, 10) || 0; + let dockerCreated = false; + let dockerManager; + if (config.dockerMysql?.enabled) { + // Start docker container if not running and then stop it + const mysqlDockerConfig = this.getDockerConfigMySQL(config); + dockerManager = this.getPluginInstance('docker')?.getDockerManager(); + if (!dockerManager) { + dockerCreated = true; + mysqlDockerConfig.removeOnExit = true; + dockerManager = new plugin_docker_1.DockerManagerOfOwnContainers({ + logger: { + level: 'silly', + silly: this.log.silly.bind(this.log), + debug: this.log.debug.bind(this.log), + info: this.log.info.bind(this.log), + warn: this.log.warn.bind(this.log), + error: this.log.error.bind(this.log), + }, + namespace: this.namespace, + adapterDir: `${__dirname}/../`, + }, [mysqlDockerConfig]); + } + } + if (config.dbtype === 'postgresql') { + postgreSQLOptions = { + host: config.host, + user: config.user || '', + password: config.password || '', + port: config.port || undefined, + database: 'postgres', + ssl: config.encrypt + ? { + rejectUnauthorized: !!config.rejectUnauthorized, + } + : undefined, + }; + } + else if (config.dbtype === 'mssql') { + msSQLOptions = { + server: config.host, // needed for MSSQL + user: config.user || '', + password: config.password || '', + port: config.port || undefined, + options: { + encrypt: !!config.encrypt, + trustServerCertificate: !config.rejectUnauthorized, + }, + }; + } + else if (config.dbtype === 'mysql') { + mySQLOptions = { + host: config.host, // needed for PostgreSQL , MySQL + user: config.user || '', + password: config.password || '', + port: config.port || undefined, + ssl: config.encrypt + ? { + rejectUnauthorized: !!config.rejectUnauthorized, + } + : undefined, + }; + } + else if (config.dbtype === 'sqlite') { + sqLiteOptions = { fileName: this.getSqlLiteDir(config.fileName) }; + } + try { + let client; + if (config.dbtype === 'postgresql' && postgreSQLOptions) { + client = new postgresql_client_1.PostgreSQLClient(postgreSQLOptions); + } + else if (config.dbtype === 'mssql' && msSQLOptions) { + client = new mssql_client_1.MSSQLClient(msSQLOptions); + } + else if (config.dbtype === 'mysql' && mySQLOptions) { + client = new mysql_client_1.MySQL2Client(mySQLOptions); + } + else if (config.dbtype === 'sqlite' && sqLiteOptions) { + client = new sqlite3_client_1.SQLite3Client(sqLiteOptions); + } + else { + this.sendTo(msg.from, msg.command, { error: 'Unknown DB type' }, msg.callback); + return; + } + client.on?.('error', (err) => this.log.warn(`SQL client error: ${err}`)); + this.testConnectTimeout = setTimeout(() => { + this.testConnectTimeout = null; + this.sendTo(msg.from, msg.command, { error: 'connect timeout' }, msg.callback); + }, 5000); + const err = await new Promise(resolve => client.connect((err) => { + if (err) { + if (this.testConnectTimeout) { + clearTimeout(this.testConnectTimeout); + this.testConnectTimeout = null; + } + this.sendTo(msg.from, msg.command, { error: `${err.code} ${err.toString()}` }, msg.callback); + return; + } + client.execute('SELECT 2 + 3 AS x', (err /* , rows, fields */) => { + client.disconnect(); + resolve(err); + }); + })); + if (dockerCreated && dockerManager) { + try { + await dockerManager.destroy(); + dockerManager = undefined; + dockerCreated = false; + } + catch (e) { + this.log.error(`Cannot stop docker container: ${e}`); + } + } + if (this.testConnectTimeout) { + clearTimeout(this.testConnectTimeout); + this.testConnectTimeout = null; + this.sendTo(msg.from, msg.command, { error: err?.toString() || null }, msg.callback); + return; + } + } + catch (ex) { + if (this.testConnectTimeout) { + clearTimeout(this.testConnectTimeout); + this.testConnectTimeout = null; + } + if (dockerCreated && dockerManager) { + try { + await dockerManager.destroy(); + } + catch (e) { + this.log.error(`Cannot stop docker container: ${e}`); + } + } + if (ex.toString() === 'TypeError: undefined is not a function') { + this.sendTo(msg.from, msg.command, { error: 'Node.js DB driver could not be installed.' }, msg.callback); + } + else { + this.sendTo(msg.from, msg.command, { error: ex.toString() }, msg.callback); + } + } + } + destroyDB(msg) { + try { + this.allScripts(this.sqlFuncs.destroy(this.config.dbname), 0, err => { + if (err) { + this.log.error(err.toString()); + this.sendTo(msg.from, msg.command, { error: err.toString() }, msg.callback); + } + else { + this.sendTo(msg.from, msg.command, { error: null, result: 'deleted' }, msg.callback); + // restart adapter + setTimeout(() => this.getForeignObject(`system.adapter.${this.namespace}`, (err, obj) => { + if (!err && obj) { + void this.setForeignObject(obj._id, obj); + } + else { + this.log.error(`Cannot read object "system.adapter.${this.namespace}": ${err || 'Config does not exist'}`); + this.stop ? void this.stop() : void this.terminate(); + } + }), 2000); + } + }); + } + catch (ex) { + return this.sendTo(msg.from, msg.command, { error: ex.toString() }, msg.callback); + } + } + _userQuery(msg, callback) { + try { + if (typeof msg.message !== 'string' || !msg.message.length) { + throw new Error('No query provided'); + } + this.log.debug(msg.message); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.sendTo(msg.from, msg.command, { error: err?.toString() || 'No client' }, msg.callback); + this.returnClientToPool(client); + callback?.(); + } + else { + client.execute(msg.message, (err, rows /* , fields */) => { + this.returnClientToPool(client); + //convert ts for postgresql and ms sqlserver + if (!err && rows?.[0] && typeof rows[0].ts === 'string') { + for (let i = 0; i < rows.length; i++) { + rows[i].ts = parseInt(rows[i].ts, 10); + } + } + this.sendTo(msg.from, msg.command, { error: err ? err.toString() : null, result: rows }, msg.callback); + callback?.(); + }); + } + }); + } + catch (err) { + this.sendTo(msg.from, msg.command, { error: err.toString() }, msg.callback); + callback?.(); + } + } + // execute custom query + query(msg) { + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + this.sendTo(msg.from, msg.command, { error }, msg.callback); + } + else { + this.tasks.push({ operation: 'userQuery', msg }); + if (this.tasks.length === 1) { + this.processTasks(); + } + } + } + else { + this._userQuery(msg); + } + } + // one script + oneScript(script, cb) { + try { + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.clientPool?.close(); + this.activeConnections = 0; + this.clientPool = null; + this.setConnected(false); + this.log.error(err?.toString() || 'No database connection'); + return cb?.(err || new Error('No database connection')); + } + this.log.debug(script); + client.execute(script, (err) => { + this.log.debug(`Response: ${JSON.stringify(err)}`); + if (err) { + const typedError = err; + // Database 'iobroker' already exists. Choose a different database name. + if (typedError.number === 1801 || + // There is already an object named 'sources' in the database. + typedError.number === 2714) { + // do nothing + err = null; + } + else if (typedError.message?.match(/^SQLITE_ERROR: table [\w_]+ already exists$/)) { + // do nothing + err = null; + } + else if (typedError.errno == 1007 || typedError.errno == 1050) { + // if a database exists or table exists, + // do nothing + err = null; + } + else if (typedError.code === '42P04') { + // if a database exists or table exists, + // do nothing + err = null; + } + else if (typedError.code === '42P07') { + const match = script.match(/CREATE\s+TABLE\s+(\w*)\s+\(/); + if (match) { + this.log.debug(`OK. Table "${match[1]}" yet exists`); + err = null; + } + else { + this.log.error(script); + this.log.error(err.toString()); + } + } + else if (script.startsWith('CREATE INDEX')) { + this.log.info('Ignore Error on Create index. You might want to create the index yourself!'); + this.log.info(script); + this.log.info(`${typedError.code}: ${err}`); + err = null; + } + else { + this.log.error(script); + this.log.error(err.toString()); + } + } + this.returnClientToPool(client); + cb?.(err); + }); + }); + } + catch (ex) { + this.log.error(ex); + cb?.(ex); + } + } + // all scripts + allScripts(scripts, index, cb) { + index ||= 0; + if (scripts && index < scripts.length) { + this.oneScript(scripts[index], err => { + if (err) { + cb?.(err); + } + else { + this.allScripts(scripts, index + 1, cb); + } + }); + } + else { + cb?.(); + } + } + finish(callback) { + let count = 0; + const now = Date.now(); + const allFinished = () => { + if (this.clientPool) { + this.clientPool.close(); + this.activeConnections = 0; + this.clientPool = null; + this.setConnected(false); + } + if (typeof this.finished === 'object') { + setTimeout((cb) => { + for (let f = 0; f < cb.length; f++) { + typeof cb[f] === 'function' && cb[f](); + } + }, 500, this.finished); + this.finished = true; + } + }; + const finishId = (id) => { + if (!this.sqlDPs[id]) { + return; + } + if (this.sqlDPs[id].relogTimeout) { + clearTimeout(this.sqlDPs[id].relogTimeout); + this.sqlDPs[id].relogTimeout = null; + } + if (this.sqlDPs[id].timeout) { + clearTimeout(this.sqlDPs[id].timeout); + this.sqlDPs[id].timeout = null; + } + const state = this.sqlDPs[id].state ? { ...this.sqlDPs[id].state } : null; + if (this.sqlDPs[id].skipped && + !(this.sqlDPs[id].config && this.sqlDPs[id].config.disableSkippedValueLogging)) { + count++; + this.pushValueIntoDB(id, this.sqlDPs[id].skipped, false, true, () => { + if (!--count) { + this.pushValuesIntoDB(id, this.sqlDPs[id].list, () => { + allFinished(); + }); + } + }); + this.sqlDPs[id].skipped = null; + } + const nullValue = { + ack: true, + val: null, + ts: now, + lc: now, + q: 0x40, + from: `system.adapter.${this.namespace}`, + }; + if (this.sqlDPs[id].config && this.config.writeNulls) { + if (this.sqlDPs[id].config.changesOnly && state && state.val !== null) { + count++; + ((_id, _state, _nullValue) => { + _state.ts = now; + _state.from = `system.adapter.${this.namespace}`; + nullValue.ts += 4; + nullValue.lc += 4; // because of MS SQL + this.log.debug(`Write 1/2 "${_state.val}" _id: ${_id}`); + this.pushValueIntoDB(_id, _state, false, true, () => { + // terminate values with null to indicate adapter stop. timestamp + 1 + this.log.debug(`Write 2/2 "null" _id: ${_id}`); + this.pushValueIntoDB(_id, _nullValue, false, true, () => { + if (!--count) { + this.pushValuesIntoDB(id, this.sqlDPs[id].list, () => { + allFinished(); + }); + } + }); + }); + })(id, state, nullValue); + } + else { + // terminate values with null to indicate adapter stop. timestamp + 1 + count++; + this.log.debug(`Write 0 NULL _id: ${id}`); + this.pushValueIntoDB(id, nullValue, false, true, () => { + if (!--count) { + this.pushValuesIntoDB(id, this.sqlDPs[id].list, () => { + allFinished(); + }); + } + }); + } + } + }; + if (!this.subscribeAll) { + for (const _id in this.sqlDPs) { + if (Object.prototype.hasOwnProperty.call(this.sqlDPs, _id) && + Object.prototype.hasOwnProperty.call(this.sqlDPs, this.sqlDPs[_id].realId)) { + this.unsubscribeForeignStates(this.sqlDPs[_id].realId); + } + } + } + else { + this.subscribeAll = false; + this.unsubscribeForeignStates('*'); + } + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = null; + } + if (this.testConnectTimeout) { + clearTimeout(this.testConnectTimeout); + this.testConnectTimeout = null; + } + if (this.dpOverviewTimeout) { + clearTimeout(this.dpOverviewTimeout); + this.dpOverviewTimeout = null; + } + if (this.bufferChecker) { + clearInterval(this.bufferChecker); + this.bufferChecker = null; + } + if (this.finished) { + if (callback) { + if (this.finished === true) { + callback(); + } + else { + this.finished.push(callback); + } + } + return; + } + this.finished = [callback]; + let dpcount = 0; + let delay = 0; + for (const id in this.sqlDPs) { + if (!Object.prototype.hasOwnProperty.call(this.sqlDPs, id)) { + continue; + } + dpcount++; + delay += dpcount % 50 === 0 ? 1000 : 0; + setTimeout(finishId, delay, id); + } + if (!dpcount && callback) { + if (this.clientPool) { + this.clientPool.close(); + this.activeConnections = 0; + this.clientPool = null; + this.setConnected(false); + } + callback(); + } + } + processMessage(msg) { + if (msg.command === 'features') { + this.sendTo(msg.from, msg.command, { supportedFeatures: ['update', 'delete', 'deleteRange', 'deleteAll', 'storeState'] }, msg.callback); + } + else if (msg.command === 'getHistory') { + this.getHistorySql(msg); + } + else if (msg.command === 'getCounter') { + this.getCounterDiff(msg); + } + else if (msg.command === 'test') { + void this.testConnection(msg); + } + else if (msg.command === 'destroy') { + this.destroyDB(msg); + } + else if (msg.command === 'query') { + this.query(msg); + } + else if (msg.command === 'update') { + this.updateState(msg); + } + else if (msg.command === 'delete') { + this.deleteHistoryEntry(msg); + } + else if (msg.command === 'deleteAll') { + this.deleteStateAll(msg); + } + else if (msg.command === 'deleteRange') { + this.deleteHistoryEntry(msg); + } + else if (msg.command === 'storeState') { + this.storeState(msg).catch(e => this.log.error(`Cannot store state: ${e}`)); + } + else if (msg.command === 'getDpOverview') { + this.getDpOverview(msg); + } + else if (msg.command === 'enableHistory') { + this.enableHistory(msg); + } + else if (msg.command === 'disableHistory') { + this.disableHistory(msg); + } + else if (msg.command === 'getEnabledDPs') { + this.getEnabledDPs(msg); + } + else if (msg.command === 'stopInstance') { + this.finish(() => { + if (msg.callback) { + this.sendTo(msg.from, msg.command, 'stopped', msg.callback); + setTimeout(() => (this.stop ? this.stop() : this.terminate?.()), 200); + } + }); + } + } + processStartValues(callback) { + if (this.tasksStart?.length) { + const task = this.tasksStart.shift(); + const sqlDP = this.sqlDPs[task.id]; + if (sqlDP.config.changesOnly) { + void this.getForeignState(sqlDP.realId, (err, state) => { + const now = task.now || Date.now(); + this.pushHistory(task.id, { + val: null, + ts: state ? now - 4 : now, // 4 is because of MS SQL + lc: state ? now - 4 : now, // 4 is because of MS SQL + ack: true, + q: 0x40, + from: `system.adapter.${this.namespace}`, + }); + if (state) { + state.ts = now; + state.lc = now; + state.from = `system.adapter.${this.namespace}`; + this.pushHistory(task.id, state); + } + setImmediate(() => this.processStartValues()); + }); + } + else { + const now = Date.now(); + this.pushHistory(task.id, { + val: null, + ts: task.now || now, + lc: task.now || now, + ack: true, + q: 0x40, + from: `system.adapter.${this.namespace}`, + }); + setImmediate(() => this.processStartValues()); + } + if (sqlDP.config?.changesOnly && sqlDP.config.changesRelogInterval > 0) { + if (sqlDP.relogTimeout) { + clearTimeout(sqlDP.relogTimeout); + } + sqlDP.relogTimeout = setTimeout((id) => this.reLogHelper(id), sqlDP.config.changesRelogInterval * 500 * Math.random() + sqlDP.config.changesRelogInterval * 500, task.id); + } + } + else { + callback && callback(); + } + } + writeNulls(id, now) { + if (!id) { + now = Date.now(); + Object.keys(this.sqlDPs) + .filter(_id => this.sqlDPs[_id] && this.sqlDPs[_id].config) + .forEach(_id => this.writeNulls(_id, now)); + } + else { + now ||= Date.now(); + this.tasksStart.push({ id, now }); + if (this.tasksStart.length === 1 && this.sqlConnected) { + this.processStartValues(); + } + } + } + pushHistory(id, state, timerRelog) { + timerRelog ||= false; + // Push into DB + if (this.sqlDPs[id]) { + const settings = this.sqlDPs[id].config; + if (!settings || !state) { + return; + } + if (state && state.val === undefined) { + return this.log.warn(`state value undefined received for ${id} which is not allowed. Ignoring.`); + } + if (typeof state.val === 'string' && settings.storageType !== 'String') { + if (isFinite(state.val)) { + state.val = parseFloat(state.val); + } + } + if (settings.enableDebugLogs) { + this.log.debug(`new value received for ${id} (storageType ${settings.storageType}), new-value=${state.val}, ts=${state.ts}, relog=${timerRelog}`); + } + let ignoreDebounce = false; + if (!timerRelog) { + const valueUnstable = !!this.sqlDPs[id].timeout; + // When a debounce timer runs and the value is the same as the last one, ignore it + if (this.sqlDPs[id].timeout && state.ts !== state.lc) { + settings.enableDebugLogs && + this.log.debug(`value not changed debounce ${id}, value=${state.val}, ts=${state.ts}, debounce timer keeps running`); + return; + } + else if (this.sqlDPs[id].timeout) { + // if value changed, clear timer + settings.enableDebugLogs && + this.log.debug(`value changed during debounce time ${id}, value=${state.val}, ts=${state.ts}, debounce timer restarted`); + clearTimeout(this.sqlDPs[id].timeout); + this.sqlDPs[id].timeout = null; + } + if (!valueUnstable && + settings.blockTime && + this.sqlDPs[id].state && + this.sqlDPs[id].state.ts + settings.blockTime > state.ts) { + settings.enableDebugLogs && + this.log.debug(`value ignored blockTime ${id}, value=${state.val}, ts=${state.ts}, lastState.ts=${this.sqlDPs[id].state.ts}, blockTime=${settings.blockTime}`); + return; + } + if (settings.ignoreZero && (state.val === undefined || state.val === null || state.val === 0)) { + if (settings.enableDebugLogs) { + this.log.debug(`value ignore because zero or null ${id}, new-value=${state.val}, ts=${state.ts}`); + } + return; + } + if (typeof settings.ignoreBelowNumber === 'number' && + typeof state.val === 'number' && + state.val < settings.ignoreBelowNumber) { + if (settings.enableDebugLogs) { + this.log.debug(`value ignored because below ${settings.ignoreBelowNumber} for ${id}, new-value=${state.val}, ts=${state.ts}`); + } + return; + } + if (typeof settings.ignoreAboveNumber === 'number' && + typeof state.val === 'number' && + state.val > settings.ignoreAboveNumber) { + if (settings.enableDebugLogs) { + this.log.debug(`value ignored because above ${settings.ignoreAboveNumber} for ${id}, new-value=${state.val}, ts=${state.ts}`); + } + return; + } + if (this.sqlDPs[id].state && settings.changesOnly) { + if (!settings.changesRelogInterval) { + if ((this.sqlDPs[id].state.val !== null || state.val === null) && state.ts !== state.lc) { + // remember new timestamp + if (!valueUnstable && !settings.disableSkippedValueLogging) { + this.sqlDPs[id].skipped = state; + } + settings.enableDebugLogs && + this.log.debug(`value not changed ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); + return; + } + } + else if (this.sqlDPs[id].lastLogTime) { + if ((this.sqlDPs[id].state.val !== null || state.val === null) && + state.ts !== state.lc && + Math.abs(this.sqlDPs[id].lastLogTime - state.ts) < settings.changesRelogInterval * 1000) { + // remember new timestamp + if (!valueUnstable && !settings.disableSkippedValueLogging) { + this.sqlDPs[id].skipped = state; + } + settings.enableDebugLogs && + this.log.debug(`value not changed ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); + return; + } + if (state.ts !== state.lc) { + settings.enableDebugLogs && + this.log.debug(`value-not-changed-relog ${id}, value=${state.val}, lastLogTime=${this.sqlDPs[id].lastLogTime}, ts=${state.ts}`); + ignoreDebounce = true; + } + } + if (typeof state.val === 'number') { + if (this.sqlDPs[id].state.val !== null && + settings.changesMinDelta && + Math.abs(this.sqlDPs[id].state.val - state.val) < settings.changesMinDelta) { + if (!valueUnstable && !settings.disableSkippedValueLogging) { + this.sqlDPs[id].skipped = state; + } + if (settings.enableDebugLogs) { + this.log.debug(`Min-Delta not reached ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); + } + return; + } + if (settings.changesMinDelta && settings.enableDebugLogs) { + this.log.debug(`Min-Delta reached ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); + } + } + else if (settings.enableDebugLogs) { + this.log.debug(`Min-Delta ignored because no number ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); + } + } + } + if (settings.counter && this.sqlDPs[id].state) { + if (this.sqlDPs[id].type !== types.number) { + this.log.error('Counter must have type "number"!'); + } + else if (state.val === null || + this.sqlDPs[id].state.val === null || + state.val < this.sqlDPs[id].state.val) { + // if the actual value is less then last seen counter, store both values + this.pushValueIntoDB(id, this.sqlDPs[id].state, true); + this.pushValueIntoDB(id, state, true); + } + } + if (this.sqlDPs[id].relogTimeout) { + clearTimeout(this.sqlDPs[id].relogTimeout); + this.sqlDPs[id].relogTimeout = null; + } + if (timerRelog) { + state = { ...state }; + state.ts = Date.now(); + state.from = `system.adapter.${this.namespace}`; + settings.enableDebugLogs && + this.log.debug(`timed-relog ${id}, value=${state.val}, lastLogTime=${this.sqlDPs[id].lastLogTime}, ts=${state.ts}`); + ignoreDebounce = true; + } + else { + if (settings.changesOnly && this.sqlDPs[id].skipped) { + settings.enableDebugLogs && + this.log.debug(`Skipped value logged ${id}, value=${this.sqlDPs[id].skipped.val}, ts=${this.sqlDPs[id].skipped.ts}`); + this.pushHelper(id, this.sqlDPs[id].skipped); + this.sqlDPs[id].skipped = null; + } + if (this.sqlDPs[id].state && + ((this.sqlDPs[id].state.val === null && state.val !== null) || + (this.sqlDPs[id].state.val !== null && state.val === null))) { + ignoreDebounce = true; + } + else if (!this.sqlDPs[id].state && state.val === null) { + ignoreDebounce = true; + } + } + if (settings.debounceTime && !ignoreDebounce && !timerRelog) { + // Discard changes in the debounce time to store last stable value + this.sqlDPs[id].timeout && clearTimeout(this.sqlDPs[id].timeout); + this.sqlDPs[id].timeout = setTimeout((id, state) => { + if (!this.sqlDPs[id]) { + return; + } + this.sqlDPs[id].timeout = null; + this.sqlDPs[id].state = state; + this.sqlDPs[id].lastLogTime = state.ts; + if (settings.enableDebugLogs) { + this.log.debug(`Value logged ${id}, value=${this.sqlDPs[id].state.val}, ts=${this.sqlDPs[id].state.ts}`); + } + this.pushHelper(id); + if (settings.changesOnly && settings.changesRelogInterval > 0) { + this.sqlDPs[id].relogTimeout = setTimeout((_id) => this.reLogHelper(_id), settings.changesRelogInterval * 1000, id); + } + }, settings.debounceTime, id, state); + } + else { + if (!timerRelog) { + this.sqlDPs[id].state = state; + } + this.sqlDPs[id].lastLogTime = state.ts; + if (settings.enableDebugLogs) { + this.log.debug(`Value logged ${id}, value=${this.sqlDPs[id].state?.val}, ts=${this.sqlDPs[id].state?.ts}`); + } + this.pushHelper(id, state); + if (settings.changesOnly && settings.changesRelogInterval > 0) { + this.sqlDPs[id].relogTimeout = setTimeout((_id) => this.reLogHelper(_id), settings.changesRelogInterval * 1000, id); + } + } + } + } + reLogHelper(_id) { + if (!this.sqlDPs[_id]) { + this.log.info(`non-existing id ${_id}`); + } + else { + this.sqlDPs[_id].relogTimeout = null; + if (this.sqlDPs[_id].skipped) { + this.pushHistory(_id, this.sqlDPs[_id].skipped, true); + } + else if (this.sqlDPs[_id].state) { + this.pushHistory(_id, this.sqlDPs[_id].state, true); + } + else { + void this.getForeignState(this.sqlDPs[_id].realId, (err, state) => { + if (err) { + this.log.info(`init timed Relog: can not get State for ${_id} : ${err}`); + } + else if (!state) { + this.log.info(`init timed Relog: disable relog because state not set so far for ${_id}: ${JSON.stringify(state)}`); + } + else { + this.log.debug(`init timed Relog: getState ${_id}: Value=${state.val}, ack=${state.ack}, ts=${state.ts}, lc=${state.lc}`); + this.sqlDPs[_id].state = state; + this.pushHistory(_id, this.sqlDPs[_id].state, true); + } + }); + } + } + } + pushHelper(_id, state, cb) { + if (!this.sqlDPs[_id] || (!this.sqlDPs[_id].state && !state)) { + return; + } + state ||= this.sqlDPs[_id].state; + const _settings = this.sqlDPs[_id].config || {}; + let val = state.val; + if (val !== null && (typeof val === 'object' || typeof val === 'undefined')) { + val = JSON.stringify(val); + } + if (val !== null && val !== undefined) { + if (_settings.enableDebugLogs) { + this.log.debug(`Datatype ${_id}: Currently: ${typeof val}, StorageType: ${_settings.storageType}`); + } + if (typeof val === 'string' && _settings.storageType !== 'String') { + _settings.enableDebugLogs && this.log.debug(`Do Automatic Datatype conversion for ${_id}`); + if (isFinite(val)) { + val = parseFloat(val); + } + else if (val === 'true') { + val = true; + } + else if (val === 'false') { + val = false; + } + } + if (_settings.storageType === 'String' && typeof val !== 'string') { + val = val.toString(); + } + else if (_settings.storageType === 'Number' && typeof val !== 'number') { + if (typeof val === 'boolean') { + val = val ? 1 : 0; + } + else { + return this.log.info(`Do not store value "${val}" for ${_id} because no number`); + } + } + else if (_settings.storageType === 'Boolean' && typeof val !== 'boolean') { + val = !!val; + } + } + else { + _settings.enableDebugLogs && this.log.debug(`Datatype ${_id}: Currently: null`); + } + state.val = val; + this.pushValueIntoDB(_id, state, false, false, cb); + } + getAllIds(cb) { + const query = this.sqlFuncs.getIdSelect(this.config.dbname); + this.log.debug(query); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + return cb?.(err || new Error('No client')); + } + client.execute(query, (err, rows) => { + this.returnClientToPool(client); + if (err) { + this.log.error(`Cannot select ${query}: ${err}`); + return cb?.(err); + } + if (rows?.length) { + let id; + for (let r = 0; r < rows.length; r++) { + id = rows[r].name; + this.sqlDPs[id] ||= {}; + this.sqlDPs[id].index = rows[r].id; + if (rows[r].type !== null) { + this.sqlDPs[id].dbType = rows[r].type; + } + } + } + cb?.(); + }); + }); + } + getAllFroms(cb) { + const query = this.sqlFuncs.getFromSelect(this.config.dbname); + this.log.debug(query); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + return cb?.(err || new Error('No client')); + } + client.execute(query, (err, rows /* , fields */) => { + this.returnClientToPool(client); + if (err) { + this.log.error(`Cannot select ${query}: ${err}`); + return cb?.(err); + } + if (rows?.length) { + for (let r = 0; r < rows.length; r++) { + this.from[rows[r].name] = rows[r].id; + } + } + cb?.(); + }); + }); + } + _checkRetention(query, cb) { + this.log.debug(query); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + this.log.error(err?.toString() || 'No client'); + cb?.(); + } + else { + client.execute(query, (err) => { + this.returnClientToPool(client); + if (err) { + this.log.warn(`Retention: Cannot delete ${query}: ${err}`); + } + cb?.(); + }); + } + }); + } + checkRetention(id) { + if (this.sqlDPs[id]?.config?.retention) { + const dt = Date.now(); + // check every 6 hours + if (!this.sqlDPs[id].lastCheck || dt - this.sqlDPs[id].lastCheck >= 21600000 /* 6 hours */) { + this.sqlDPs[id].lastCheck = dt; + if (!dbNames[this.sqlDPs[id].type]) { + this.log.error(`No type ${this.sqlDPs[id].type} found for ${id}. Retention is not possible.`); + } + else { + const query = this.sqlFuncs.retention(this.config.dbname, this.sqlDPs[id].index, dbNames[this.sqlDPs[id].type], this.sqlDPs[id].config.retention); + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + return this.log.error(`Cannot queue new requests, because more than ${MAX_TASKS}`); + } + const start = this.tasks.length === 1; + this.tasks.push({ operation: 'delete', query }); + // delete counters too + if (this.sqlDPs[id] && this.sqlDPs[id].type === 0) { + // 0 === number + const query = this.sqlFuncs.retention(this.config.dbname, this.sqlDPs[id].index, 'ts_counter', this.sqlDPs[id].config.retention); + this.tasks.push({ operation: 'delete', query }); + } + start && this.processTasks(); + } + else { + this._checkRetention(query, () => { + // delete counters too + if (this.sqlDPs[id] && this.sqlDPs[id].type === 0) { + // 0 === number + const query = this.sqlFuncs.retention(this.config.dbname, this.sqlDPs[id].index, 'ts_counter', this.sqlDPs[id].config.retention); + this._checkRetention(query); + } + }); + } + } + } + } + } + _insertValueIntoDB(query, id, cb) { + this.log.debug(query); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + this.log.error(err?.toString() || 'No client'); + cb?.(); // BF asked (2021.12.14): may be return here err? + } + else { + client.execute(query, (err /* , rows, fields */) => { + this.returnClientToPool(client); + if (err) { + this.log.error(`Cannot insert ${query}: ${err} (id: ${id})`); + } + else { + this.checkRetention(id); + } + cb?.(); // BF asked (2021.12.14): may be return here err? + }); + } + }); + } + _executeQuery(query, id, cb) { + this.log.debug(query); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + this.log.error(err?.toString() || 'No client'); + cb?.(); + } + else { + client.execute(query, (err) => { + this.returnClientToPool(client); + if (err) { + this.log.error(`Cannot query ${query}: ${err} (id: ${id})`); + } + cb?.(); + }); + } + }); + } + processReadTypes() { + if (this.tasksReadType?.length) { + const task = this.tasksReadType[0]; + if (!this.sqlDPs[task.id]) { + this.log.warn(`Ignore type lookup for ${task.id} because not enabled anymore`); + task.cb?.(new Error(`Ignore type lookup for ${task.id} because not enabled anymore`)); + task.cb = null; + setImmediate(() => { + this.tasksReadType.shift(); + this.processReadTypes(); + }); + return; + } + const sqlDP = this.sqlDPs[task.id]; + this.log.debug(`Type set in Def for ${task.id}: ${this.sqlDPs[task.id].config && this.sqlDPs[task.id].config.storageType}`); + if (sqlDP.config?.storageType) { + sqlDP.type = types[sqlDP.config.storageType.toLowerCase()]; + this.log.debug(`Type (from Def) for ${task.id}: ${sqlDP.type}`); + this.processVerifyTypes(task); + } + else if (sqlDP.dbType !== undefined) { + sqlDP.type = sqlDP.dbType; + if (sqlDP.config) { + sqlDP.config.storageType = storageTypes[sqlDP.type]; + } + this.log.debug(`Type (from DB-Type) for ${task.id}: ${sqlDP.type}`); + this.processVerifyTypes(task); + } + else { + void this.getForeignObject(sqlDP.realId, (err, obj) => { + if (err) { + this.log.warn(`Error while get Object for Def for ${sqlDP.realId}: ${err}`); + } + if (!sqlDP) { + this.log.warn(`Ignore type lookup for ${task.id} because not enabled anymore`); + task.cb?.(new Error(`Ignore type lookup for ${task.id} because not enabled anymore`)); + task.cb = null; + return setImmediate(() => { + this.tasksReadType.shift(); + this.processReadTypes(); + }); + } + else if (obj?.common?.type && types[obj.common.type.toLowerCase()] !== undefined) { + // read type from object + this.log.debug(`${obj.common.type.toLowerCase()} / ${types[obj.common.type.toLowerCase()]} / ${JSON.stringify(obj.common)}`); + sqlDP.type = types[obj.common.type.toLowerCase()]; + if (sqlDP.config) { + sqlDP.config.storageType = storageTypes[sqlDP.type]; + } + this.log.debug(`Type (from Obj) for ${task.id}: ${sqlDP.type}`); + this.processVerifyTypes(task); + } + else if (sqlDP.type === undefined) { + void this.getForeignState(sqlDP.realId, (err, state) => { + if (!this.sqlDPs[task.id]) { + this.log.warn(`Ignore type lookup for ${task.id} because not enabled anymore`); + task.cb?.(new Error(`Ignore type lookup for ${task.id} because not enabled anymore`)); + task.cb = null; + return setImmediate(() => { + this.tasksReadType.shift(); + this.processReadTypes(); + }); + } + if (err && task.state) { + this.log.warn(`Fallback to type of current state value because no other valid type found`); + state = task.state; + } + if (state && + state.val !== null && + state.val !== undefined && + types[typeof state.val] !== undefined) { + this.sqlDPs[task.id].type = types[typeof state.val]; + if (this.sqlDPs[task.id].config) { + this.sqlDPs[task.id].config.storageType = storageTypes[this.sqlDPs[task.id].type]; + } + } + else { + this.log.warn(`Store data for ${task.id} as string because no other valid type found (${state ? typeof state.val : 'state not existing'})`); + this.sqlDPs[task.id].type = 1; // string + } + this.log.debug(`Type (from State) for ${task.id}: ${this.sqlDPs[task.id].type}`); + this.processVerifyTypes(task); + }); + } + else { + // all OK + task.cb?.(); + task.cb = null; + this.tasksReadType.shift(); + this.processReadTypes(); + } + }); + } + } + } + processVerifyTypes(task) { + if (this.sqlDPs[task.id].index !== undefined && + this.sqlDPs[task.id].type !== undefined && + this.sqlDPs[task.id].type !== this.sqlDPs[task.id].dbType) { + this.sqlDPs[task.id].dbType = this.sqlDPs[task.id].type; + const query = this.sqlFuncs.getIdUpdate(this.config.dbname, this.sqlDPs[task.id].index, this.sqlDPs[task.id].type); + this.log.debug(query); + return this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + this.processVerifyTypes(task); + return; + } + client.execute(query, err => { + this.returnClientToPool(client); + if (err) { + this.log.error(`error updating history config for ${task.id} to pin datatype: ${query}: ${err}`); + } + else { + this.log.info(`changed history configuration to pin detected datatype for ${task.id}`); + } + this.processVerifyTypes(task); + }); + }); + } + task.cb?.(); + task.cb = null; + setTimeout(() => { + this.tasksReadType.shift(); + this.processReadTypes(); + }, 50); + } + prepareTaskReadDbId(id, state, isCounter, cb) { + if (!this.sqlDPs[id]) { + cb?.(new Error(`${id} not active any more`)); + return; + } + const type = this.sqlDPs[id].type; + if (type === undefined) { + // Can not happen anymore + let warn; + if (state.val === null) { + warn = `Ignore null value for ${id} because no type defined till now.`; + } + else { + warn = `Cannot store values of type "${typeof state.val}" for ${id}`; + } + this.log.warn(warn); + cb?.(new Error(warn)); + return; + } + let tmpState; + // get SQL id of state + if (this.sqlDPs[id].index === undefined) { + this.sqlDPs[id].isRunning ||= []; + tmpState = { ...state }; + this.sqlDPs[id].isRunning.push({ id, state: tmpState, cb, isCounter }); + if (this.sqlDPs[id].isRunning.length === 1) { + // read or create in DB + return this.getId(id, type, (err, _id) => { + this.log.debug(`prepareTaskCheckTypeAndDbId getId Result - isRunning length = ${this.sqlDPs[id].isRunning ? this.sqlDPs[id].isRunning.length : 'none'}`); + if (err) { + this.log.warn(`Cannot get index of "${_id}": ${err}`); + this.sqlDPs[_id].isRunning?.forEach(r => r.cb?.(new Error(`Cannot get index of "${r.id}": ${err}`))); + } + else { + this.sqlDPs[_id].isRunning?.forEach(r => r.cb?.()); + } + this.sqlDPs[_id].isRunning = undefined; + }); + } + return; + } + // get from + if (!isCounter && state.from && !this.from[state.from]) { + this.isFromRunning[state.from] ||= []; + tmpState = { ...state }; + const isFromRunning = this.isFromRunning[state.from]; + isFromRunning.push({ id, state: tmpState, cb }); + if (isFromRunning.length === 1) { + // read or create in DB + return this.getFrom(state.from, (err, from) => { + this.log.debug(`prepareTaskCheckTypeAndDbId getFrom ${from} Result - isRunning length = ${this.isFromRunning[from] ? this.isFromRunning[from].length : 'none'}`); + if (err) { + this.log.warn(`Cannot get "from" for "${from}": ${err}`); + this.isFromRunning[from]?.forEach(f => f.cb?.(new Error(`Cannot get "from" for "${from}": ${err}`))); + } + else { + this.isFromRunning[from]?.forEach(f => f.cb?.()); + } + this.isFromRunning[from] = null; + }); + } + return; + } + if (state.ts) { + state.ts = parseInt(state.ts, 10); + } + try { + if (state.val !== null && typeof state.val === 'object') { + state.val = JSON.stringify(state.val); + } + } + catch { + const error = `Cannot convert the object value "${id}"`; + this.log.error(error); + cb?.(new Error(error)); + return; + } + cb?.(); + } + prepareTaskCheckTypeAndDbId(id, state, isCounter, cb) { + // check if we know about this ID + if (!this.sqlDPs[id]) { + if (cb) { + setImmediate(() => cb(new Error(`Unknown ID: ${id}`))); + } + return; + } + // Check SQL connection + if (!this.clientPool) { + this.log.warn('No Connection to database'); + if (cb) { + setImmediate(() => cb(new Error('No Connection to database'))); + } + return; + } + this.log.debug(`prepareTaskCheckTypeAndDbId CALLED for ${id}`); + // read type of value + if (this.sqlDPs[id].type !== undefined) { + this.prepareTaskReadDbId(id, state, isCounter, cb); + } + else { + // read type from DB + this.tasksReadType.push({ + id, + state, + cb: err => { + if (err) { + cb?.(err); + } + else { + this.prepareTaskReadDbId(id, state, isCounter, cb); + } + }, + }); + if (this.tasksReadType.length === 1) { + this.processReadTypes(); + } + } + } + pushValueIntoDB(id, state, isCounter, storeInCacheOnly, cb) { + if (!this.sqlDPs[id] || !state) { + return cb?.(); + } + this.log.debug(`pushValueIntoDB called for ${id} (type: ${this.sqlDPs[id].type}, ID: ${this.sqlDPs[id].index}) and state: ${JSON.stringify(state)}`); + this.prepareTaskCheckTypeAndDbId(id, state, isCounter, (err) => { + if (!this.sqlDPs[id]) { + return cb?.(); + } + this.log.debug(`pushValueIntoDB-prepareTaskCheckTypeAndDbId RESULT for ${id} (type: ${this.sqlDPs[id].type}, ID: ${this.sqlDPs[id].index}) and state: ${JSON.stringify(state)}: ${err}`); + if (err) { + return cb?.(err); + } + const type = this.sqlDPs[id].type; + // increase timestamp if last is the same + if (!isCounter && this.sqlDPs[id].ts && state.ts === this.sqlDPs[id].ts) { + state.ts++; + } + // remember last timestamp + this.sqlDPs[id].ts = state.ts; + // if it was not deleted in this time + this.sqlDPs[id].list ||= []; + this.sqlDPs[id].list.push({ + state, + from: state.from ? this.from[state.from] : 0, + table: isCounter ? 'ts_counter' : dbNames[type], + }); + const _settings = this.sqlDPs[id].config || {}; + const maxLength = _settings.maxLength !== undefined ? _settings.maxLength : this.config.maxLength || 0; + if ((cb || (_settings && this.sqlDPs[id].list.length > maxLength)) && !storeInCacheOnly) { + this.storeCached(id, cb); + } + else if (cb && storeInCacheOnly) { + setImmediate(cb); + } + }); + } + storeCached(onlyId, cb) { + let count = 0; + for (const id in this.sqlDPs) { + if (!Object.prototype.hasOwnProperty.call(this.sqlDPs, id) || (onlyId !== undefined && onlyId !== id)) { + continue; + } + const _settings = this.sqlDPs[id].config || {}; + if (_settings && this.sqlDPs[id]?.list?.length) { + if (_settings.enableDebugLogs) { + this.log.debug(`inserting ${this.sqlDPs[id].list.length} entries from ${id} to DB`); + } + const inFlightId = `${id}_${Date.now()}_${Math.random()}`; + this.sqlDPs[id].inFlight ||= {}; + this.sqlDPs[id].inFlight[inFlightId] = this.sqlDPs[id].list; + this.sqlDPs[id].list = []; + count++; + this.pushValuesIntoDB(id, this.sqlDPs[id].inFlight[inFlightId], (err) => { + if (this.sqlDPs[id]?.inFlight?.[inFlightId]) { + delete this.sqlDPs[id].inFlight[inFlightId]; + } + if (!--count && cb) { + cb?.(err); + cb = null; + } + }); + if (onlyId !== undefined) { + break; + } + } + } + if (!count && cb) { + cb(); + } + } + pushValuesIntoDB(id, list, cb) { + if (!list.length) { + if (cb) { + setImmediate(() => cb()); + } + return; + } + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + cb?.(new Error(error)); + } + else { + this.tasks.push({ operation: 'insert', index: this.sqlDPs[id].index, list, id, callback: cb }); + this.tasks.length === 1 && this.processTasks(); + } + } + else { + const query = this.sqlFuncs.insert(this.config.dbname, this.sqlDPs[id].index, list); + this._insertValueIntoDB(query, id, cb); + } + } + processTasks() { + if (this.lockTasks) { + return this.log.debug('Tries to execute task, but last one not finished!'); + } + this.lockTasks = true; + if (this.tasks.length) { + if (this.tasks[0].operation === 'query') { + const taskQuery = this.tasks[0]; + this._executeQuery(taskQuery.query, this.tasks[0].id, () => { + taskQuery.callback?.(); + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } + else if (this.tasks[0].operation === 'insert') { + const taskInsert = this.tasks[0]; + const callbacks = []; + if (taskInsert.callback) { + callbacks.push(taskInsert.callback); + } + for (let i = 1; i < this.tasks.length; i++) { + if (this.tasks[i].operation === 'insert') { + const _taskInsert = this.tasks[i]; + if (taskInsert.index === _taskInsert.index) { + taskInsert.list = taskInsert.list.concat(_taskInsert.list); + if (_taskInsert.callback) { + callbacks.push(_taskInsert.callback); + } + this.tasks.splice(i, 1); + i--; + } + } + } + const query = this.sqlFuncs.insert(this.config.dbname, this.tasks[0].index, this.tasks[0].list); + this._insertValueIntoDB(query, this.tasks[0].id, () => { + callbacks.forEach(cb => cb()); + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } + else if (this.tasks[0].operation === 'select') { + const taskSelect = this.tasks[0]; + this.#getDataFromDB(taskSelect.query, taskSelect.options, (err, rows) => { + taskSelect.callback?.(err, rows); + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } + else if (this.tasks[0].operation === 'userQuery') { + const taskUserQuery = this.tasks[0]; + this._userQuery(taskUserQuery.msg, () => { + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } + else if (this.tasks[0].operation === 'delete') { + const taskDelete = this.tasks[0]; + this._checkRetention(taskDelete.query, () => { + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } + else { + this.log.error(`unknown task: ${this.tasks[0].operation}`); + this.tasks[0].callback?.('Unknown task'); + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + } + } + } + // may be it is required to cache all the data in memory + getId(id, type, cb) { + let query = this.sqlFuncs.getIdSelect(this.config.dbname, id); + this.log.debug(query); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + cb?.(err || new Error('No client'), id); + return; + } + client.execute(query, (err, rows /* , fields */) => { + if (!this.sqlDPs[id]) { + this.returnClientToPool(client); + cb?.(new Error(`ID ${id} no longer active`), id); + return; + } + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot select ${query}: ${err}`); + cb?.(err, id); + return; + } + if (!rows?.length) { + if (type !== null && type !== undefined) { + // insert + query = this.sqlFuncs.getIdInsert(this.config.dbname, id, type); + this.log.debug(query); + client.execute(query, (err /* , rows, fields */) => { + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot insert ${query}: ${err}`); + cb?.(err, id); + } + else { + query = this.sqlFuncs.getIdSelect(this.config.dbname, id); + this.log.debug(query); + client.execute(query, (err, rows /* , fields */) => { + this.returnClientToPool(client); + if (err) { + this.log.error(`Cannot select ${query}: ${err}`); + cb?.(err, id); + } + else if (rows?.[0]) { + this.sqlDPs[id].index = rows[0].id; + this.sqlDPs[id].type = rows[0].type; + cb?.(null, id); + } + else { + this.log.error(`No result for select ${query}: after insert`); + cb?.(new Error(`No result for select ${query}: after insert`), id); + } + }); + } + }); + } + else { + this.returnClientToPool(client); + cb?.(new Error('id not found'), id); + } + } + else { + this.sqlDPs[id].index = rows[0].id; + if (rows[0].type === null || typeof rows[0].type !== 'number') { + this.sqlDPs[id].type = type === null ? 1 : type; // default string + const query = this.sqlFuncs.getIdUpdate(this.config.dbname, this.sqlDPs[id].index, this.sqlDPs[id].type); + this.log.debug(query); + client.execute(query, err => { + this.returnClientToPool(client); + if (err) { + this.log.error(`error updating history config for ${id} to pin datatype: ${query}: ${err}`); + } + else { + this.log.info(`changed history configuration to pin detected datatype for ${id}`); + } + cb?.(null, id); + }); + } + else { + this.returnClientToPool(client); + this.sqlDPs[id].type = rows[0].type; + cb?.(null, id); + } + } + }); + }); + } + // may be it is required to cache all the data in memory + getFrom(_from, cb) { + // const sources = (this.config.dbtype !== 'postgresql' ? (this.config.dbname + '.') : '') + 'sources'; + let query = this.sqlFuncs.getFromSelect(this.config.dbname, _from); + this.log.debug(query); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + return cb?.(err || new Error('No client'), _from); + } + client.execute(query, (err, rows /* , fields */) => { + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot select ${query}: ${err}`); + return cb?.(err, _from); + } + if (!rows?.length) { + // insert + query = this.sqlFuncs.getFromInsert(this.config.dbname, _from); + this.log.debug(query); + client.execute(query, err => { + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot insert ${query}: ${err}`); + return cb?.(err, _from); + } + query = this.sqlFuncs.getFromSelect(this.config.dbname, _from); + this.log.debug(query); + client.execute(query, (err, rows /* , fields */) => { + this.returnClientToPool(client); + if (err || !rows?.length) { + this.log.error(`Cannot select ${query}: ${err}`); + return cb?.(err, _from); + } + this.from[_from] = rows[0].id; + cb?.(null, _from); + }); + }); + } + else { + this.returnClientToPool(client); + this.from[_from] = rows[0].id; + cb?.(null, _from); + } + }); + }); + } + getOneCachedData(id, options, cache) { + if (this.sqlDPs[id]) { + let anyInflight = false; + let res = []; + for (const inFlightId in this.sqlDPs[id].inFlight) { + this.log.debug(`getOneCachedData: add ${this.sqlDPs[id].inFlight[inFlightId].length} inFlight datapoints for ${options.id}`); + res = res.concat(this.sqlDPs[id].inFlight[inFlightId]); + anyInflight ||= !!this.sqlDPs[id].inFlight[inFlightId].length; + } + res = res.concat(this.sqlDPs[id].list); + // todo can be optimized + if (res) { + let iProblemCount = 0; + let vLast = null; + for (let i = res.length - 1; i >= 0; i--) { + if (!res[i] || !res[i].state) { + iProblemCount++; + continue; + } + if (options.start && res[i].state.ts < options.start) { + // add one before start + cache.unshift({ ...res[i].state }); + break; + } + else if (res[i].state.ts > options.end) { + // add one after end + vLast = res[i].state; + continue; + } + if (vLast) { + cache.unshift({ ...vLast }); + vLast = null; + } + cache.unshift({ ...res[i].state }); + if (options.returnNewestEntries && + options.count && + cache.length >= options.count && + (options.aggregate === 'onchange' || !options.aggregate || options.aggregate === 'none')) { + break; + } + } + if (iProblemCount) { + this.log.warn(`getOneCachedData: got null states ${iProblemCount} times for ${options.id}`); + } + this.log.debug(`getOneCachedData: got ${res.length} datapoints for ${options.id}`); + return anyInflight; + } + this.log.debug(`getOneCachedData: datapoints for ${options.id} do not yet exist`); + } + return false; + } + getCachedData(options, callback) { + const cache = []; + let anyInflight = false; + if (options.id) { + anyInflight = this.getOneCachedData(options.id, options, cache); + } + else { + for (const id in this.sqlDPs) { + if (Object.prototype.hasOwnProperty.call(this.sqlDPs, id)) { + anyInflight ||= this.getOneCachedData(id, options, cache); + } + } + } + let earliestTs = null; + for (let c = 0; c < cache.length; c++) { + if (typeof cache[c].ts === 'string') { + cache[c].ts = parseInt(cache[c].ts, 10); + } + if (this.common?.loglevel === 'debug') { + cache[c].date = new Date(cache[c].ts); + } + if (options.ack) { + cache[c].ack = !!cache[c].ack; + } + if (typeof cache[c].val === 'number' && isFinite(cache[c].val) && options.round) { + cache[c].val = Math.round(cache[c].val * options.round) / options.round; + } + if (options.id && this.sqlDPs[options.id] && this.sqlDPs[options.id].type === 2) { + // 2 === boolean + cache[c].val = !!cache[c].val; + } + if (options.addId && !cache[c].id && options.id) { + cache[c].id = options.id; + } + if (earliestTs === null || cache[c].ts < earliestTs) { + earliestTs = cache[c].ts; + } + } + // options.length = cache.length; + callback(cache, !!options.returnNewestEntries && !!options.count && cache.length >= options.count, anyInflight, earliestTs); + } + #getDataFromDB(query, options, callback) { + this.log.debug(query); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + callback?.(err || new Error('No client')); + return; + } + client.execute(query, (err, rows /* , fields */) => { + this.returnClientToPool(client); + if (!err && rows) { + for (let c = 0; c < rows.length; c++) { + if (typeof rows[c].ts === 'string') { + rows[c].ts = parseInt(rows[c].ts, 10); + } + if (this.common?.loglevel === 'debug') { + rows[c].date = new Date(rows[c].ts); + } + if (options.ack) { + rows[c].ack = !!rows[c].ack; + } + if (typeof rows[c].val === 'number' && isFinite(rows[c].val) && options.round) { + rows[c].val = Math.round(rows[c].val * options.round) / options.round; + } + if (options.id && this.sqlDPs[options.id] && this.sqlDPs[options.id].type === 2) { + // 2 === boolean + rows[c].val = !!rows[c].val; + } + if (options.addId && !rows[c].id && options.id) { + rows[c].id = options.id; + } + } + } + callback?.(err, rows); + }); + }); + } + getDataFromDB(table, options, callback) { + const query = this.sqlFuncs.getHistory(this.config.dbname, table, options); + this.log.debug(query); + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + this.log.error(`Cannot queue new requests, because more than ${MAX_TASKS}`); + callback?.(new Error(`Cannot queue new requests, because more than ${MAX_TASKS}`)); + } + else { + this.tasks.push({ operation: 'select', query, options, callback }); + if (this.tasks.length === 1) { + this.processTasks(); + } + } + } + else { + this.#getDataFromDB(query, options, callback); + } + } + getCounterDataFromDB(options, callback) { + const query = this.sqlFuncs.getCounterDiff(this.config.dbname, options); + this.log.debug(query); + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + callback?.(new Error(error)); + } + else { + this.tasks.push({ operation: 'select', query, options, callback }); + if (this.tasks.length === 1) { + this.processTasks(); + } + } + } + else { + this.#getDataFromDB(query, options, callback); + } + } + getCounterDiff(msg) { + const id = msg.message.id; + const start = msg.message.options.start || 0; + const end = msg.message.options.end || Date.now() + 5000000; + if (!this.sqlDPs[id]) { + this.sendTo(msg.from, msg.command, { result: [], step: null, error: 'Not enabled' }, msg.callback); + } + else { + if (!this.sqlFuncs.getCounterDiff) { + this.sendTo(msg.from, msg.command, { result: [], step: null, error: 'Counter option is not enabled for this type of SQL' }, msg.callback); + } + else { + const options = { id, start, end, index: this.sqlDPs[id].index }; + this.getCounterDataFromDB(options, (err, data) => (0, aggregate_1.sendResponseCounter)(this, msg, options, err?.toString() || data || [])); + } + } + } + getHistorySql(msg) { + const startTime = Date.now(); + if (!msg.message?.options) { + return this.sendTo(msg.from, msg.command, { + error: 'Invalid call. No options for getHistory provided', + }, msg.callback); + } + let ignoreNull; + if (msg.message.options.ignoreNull === 'true') { + ignoreNull = true; + } // include nulls and replace them with last value + if (msg.message.options.ignoreNull === 'false') { + ignoreNull = false; + } // include nulls + if (msg.message.options.ignoreNull === '0') { + ignoreNull = 0; + } // include nulls and replace them with 0 + if (msg.message.options.ignoreNull !== true && + msg.message.options.ignoreNull !== false && + msg.message.options.ignoreNull !== 0) { + ignoreNull = false; + } + const logId = (msg.message.id ? msg.message.id : 'all') + Date.now() + Math.random(); + const options = { + id: msg.message.id === '*' ? null : msg.message.id, + start: msg.message.options.start, + end: msg.message.options.end || Date.now() + 5000000, + step: parseInt(msg.message.options.step, 10) || undefined, + count: parseInt(msg.message.options.count, 10), + ignoreNull, + aggregate: msg.message.options.aggregate || 'average', // One of: max, min, average, total, none, on-change + limit: parseInt(msg.message.options.limit, 10) || parseInt(msg.message.options.count, 10) || 2000, + from: msg.message.options.from || false, + q: msg.message.options.q || false, + ack: msg.message.options.ack || false, + addId: msg.message.options.addId || false, + sessionId: msg.message.options.sessionId, + returnNewestEntries: msg.message.options.returnNewestEntries || false, + percentile: msg.message.options.aggregate === 'percentile' + ? parseInt(msg.message.options.percentile, 10) || 50 + : undefined, + quantile: msg.message.options.aggregate === 'quantile' + ? parseFloat(msg.message.options.quantile) || 0.5 + : undefined, + integralUnit: msg.message.options.aggregate === 'integral' + ? parseInt(msg.message.options.integralUnit, 10) || 60 + : undefined, + integralInterpolation: msg.message.options.aggregate === 'integral' + ? msg.message.options.integralInterpolation || 'none' + : null, + removeBorderValues: msg.message.options.removeBorderValues || false, + // ID in database + index: msg.message.id === '*' ? null : this.sqlDPs[msg.message.id]?.index || null, + }; + this.log.debug(`${logId} getHistory message: ${JSON.stringify(msg.message)}`); + if (!options.count || isNaN(options.count)) { + if (options.aggregate === 'none' || options.aggregate === 'onchange') { + options.count = options.limit; + } + else { + options.count = 500; + } + } + try { + if (options.start && typeof options.start !== 'number') { + options.start = new Date(options.start).getTime(); + } + } + catch { + return this.sendTo(msg.from, msg.command, { + error: `Invalid call. Start date ${JSON.stringify(options.start)} is not a valid date`, + }, msg.callback); + } + try { + if (options.end && typeof options.end !== 'number') { + options.end = new Date(options.end).getTime(); + } + } + catch { + return this.sendTo(msg.from, msg.command, { + error: `Invalid call. End date ${JSON.stringify(options.end)} is not a valid date`, + }, msg.callback); + } + if (!options.start && options.count) { + options.returnNewestEntries = true; + } + if (msg.message.options.round !== null && + msg.message.options.round !== undefined && + msg.message.options.round !== '') { + msg.message.options.round = parseInt(msg.message.options.round, 10); + if (!isFinite(msg.message.options.round) || msg.message.options.round < 0) { + options.round = this.config.round === null ? undefined : this.config.round; + } + else { + options.round = Math.pow(10, parseInt(msg.message.options.round, 10)); + } + } + else { + options.round = this.config.round === null ? undefined : this.config.round; + } + if (options.id && this.aliasMap[options.id]) { + options.id = this.aliasMap[options.id]; + } + if ((options.aggregate === 'percentile' && options.percentile < 0) || options.percentile > 100) { + this.log.error(`Invalid percentile value: ${options.percentile}, use 50 as default`); + options.percentile = 50; + } + if ((options.aggregate === 'quantile' && options.quantile < 0) || options.quantile > 1) { + this.log.error(`Invalid quantile value: ${options.quantile}, use 0.5 as default`); + options.quantile = 0.5; + } + if (options.aggregate === 'integral' && + (typeof options.integralUnit !== 'number' || options.integralUnit <= 0)) { + this.log.error(`Invalid integralUnit value: ${options.integralUnit}, use 60s as default`); + options.integralUnit = 60; + } + if (options.id) { + this.sqlDPs[options.id] ||= {}; + } + const debugLog = !!((options.id && this.sqlDPs[options.id]?.config?.enableDebugLogs) || + this.config.enableDebugLogs); + if (options.start > options.end) { + const _end = options.end; + options.end = options.start; + options.start = _end; + } + if (!options.start && !options.count) { + options.start = Date.now() - 86400000; // - 1 day + } + if (debugLog) { + this.log.debug(`${logId} getHistory options final: ${JSON.stringify(options)}`); + } + if (options.id && this.sqlDPs[options.id].type === undefined && this.sqlDPs[options.id].dbType !== undefined) { + const storageType = this.sqlDPs[options.id].config?.storageType; + if (storageType) { + if (storageTypes.indexOf(storageType) === this.sqlDPs[options.id].dbType) { + if (debugLog) { + this.log.debug(`${logId} For getHistory for id ${options.id}: Type empty, use storageType dbType ${this.sqlDPs[options.id].dbType}`); + } + this.sqlDPs[options.id].type = this.sqlDPs[options.id].dbType; + } + } + else { + if (debugLog) { + this.log.debug(`${logId} For getHistory for id ${options.id}: Type empty, use dbType ${this.sqlDPs[options.id].dbType}`); + } + this.sqlDPs[options.id].type = this.sqlDPs[options.id].dbType; + } + } + if (options.id && this.sqlDPs[options.id].index === undefined) { + // read or create in DB + return this.getId(options.id, null, err => { + if (err) { + this.log.warn(`Cannot get index of "${options.id}": ${err}`); + (0, aggregate_1.sendResponse)(this, msg, options.id, options, [], startTime); + } + else { + this.getHistorySql(msg); + } + }); + } + if (options.id && this.sqlDPs[options.id].type === undefined) { + this.log.warn(`For getHistory for id "${options.id}": Type empty. Need to write data first. Index = ${this.sqlDPs[options.id].index}`); + (0, aggregate_1.sendResponse)(this, msg, options.id, options, 'Please wait till next data record is logged and reload.', startTime); + return; + } + // if specific id requested + if (options.id) { + const type = this.sqlDPs[options.id].type; + options.index = this.sqlDPs[options.id].index; + this.getCachedData(options, (cacheData, isFull, includesInFlightData, earliestTs) => { + if (debugLog) { + this.log.debug(`${logId} after getCachedData: length = ${cacheData.length}, isFull=${isFull}`); + } + // if all data read + if (isFull && + cacheData.length && + (options.aggregate === 'onchange' || !options.aggregate || options.aggregate === 'none')) { + cacheData.sort(sortByTs); + if (options.count && cacheData.length > options.count && options.aggregate === 'none') { + cacheData.splice(0, cacheData.length - options.count); + if (debugLog) { + this.log.debug(`${logId} cut cacheData to ${options.count} values`); + } + } + this.log.debug(`${logId} Send: ${cacheData.length} values in: ${Date.now() - startTime}ms`); + this.sendTo(msg.from, msg.command, { + result: cacheData, + step: null, + error: null, + }, msg.callback); + } + else { + const origEnd = options.end; + if (includesInFlightData && earliestTs) { + options.end = earliestTs; + } + // if not all data read + this.getDataFromDB(dbNames[type], options, (err, data) => { + if (!err && data) { + options.end = origEnd; + if (options.aggregate === 'none' && options.count && options.returnNewestEntries) { + cacheData = cacheData.reverse(); + data = cacheData.concat(data); + } + else { + data = data.concat(cacheData); + } + if (debugLog) { + this.log.debug(`${logId} after getDataFromDB: length = ${data.length}`); + } + if (options.count && + data.length > options.count && + options.aggregate === 'none' && + !options.returnNewestEntries) { + if (options.start) { + for (let i = 0; i < data.length; i++) { + if (data[i].ts < options.start) { + data.splice(i, 1); + i--; + } + else { + break; + } + } + } + data.splice(options.count); + if (debugLog) { + this.log.debug(`${logId} pre-cut data to ${options.count} oldest values`); + } + } + data.sort(sortByTs); + } + try { + (0, aggregate_1.sendResponse)(this, msg, options.id, options, err?.toString() || data || [], startTime, debugLog ? logId : undefined); + } + catch (e) { + (0, aggregate_1.sendResponse)(this, msg, options.id, options, e.toString(), startTime); + } + }); + } + }); + } + else { + // if all IDs requested + let rows = []; + let count = 0; + this.getCachedData(options, (cacheData, isFull, includesInFlightData, earliestTs) => { + if (isFull && cacheData.length) { + cacheData.sort(sortByTs); + (0, aggregate_1.sendResponse)(this, msg, '', options, cacheData, startTime, debugLog ? logId : undefined); + } + else { + if (includesInFlightData && earliestTs) { + options.end = earliestTs; + } + for (let db = 0; db < dbNames.length; db++) { + count++; + this.getDataFromDB(dbNames[db], options, (err, data) => { + if (data) { + rows = rows.concat(data); + } + if (!--count) { + rows.sort(sortByTs); + try { + (0, aggregate_1.sendResponse)(this, msg, '', options, rows, startTime); + } + catch (e) { + (0, aggregate_1.sendResponse)(this, msg, '', options, e.toString(), startTime); + } + } + }); + } + } + }); + } + } + update(id, state, cb) { + // first try to find the value in not yet saved data + let found = false; + if (this.sqlDPs[id]) { + const res = this.sqlDPs[id].list; + if (res) { + for (let i = res.length - 1; i >= 0; i--) { + if (res[i].state.ts === state.ts) { + if (state.val !== undefined) { + res[i].state.val = state.val; + } + if (state.q !== undefined && res[i].state.q !== undefined) { + res[i].state.q = state.q; + } + if (state.from !== undefined && res[i].from !== undefined) { + res[i].state.from = state.from; + } + if (state.ack !== undefined) { + res[i].state.ack = state.ack; + } + found = true; + break; + } + } + } + } + if (!found) { + this.prepareTaskCheckTypeAndDbId(id, state, false, (err) => { + if (err) { + return cb?.(err); + } + const type = this.sqlDPs[id].type; + const query = this.sqlFuncs.update(this.config.dbname, this.sqlDPs[id].index, state, this.from[state.from], dbNames[type]); + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + cb?.(new Error(error)); + } + else { + this.tasks.push({ operation: 'query', query, id, callback: cb }); + this.tasks.length === 1 && this.processTasks(); + } + } + else { + this._executeQuery(query, id, cb); + } + }); + } + else { + cb?.(); + } + } + #delete(id, state, cb) { + // first try to find the value in not yet saved data + let found = false; + if (this.sqlDPs[id]) { + const res = this.sqlDPs[id].list; + if (res) { + if (!state.ts && !state.start && !state.end) { + this.sqlDPs[id].list = []; + } + else { + for (let i = res.length - 1; i >= 0; i--) { + if (state.start && state.end) { + if (res[i].state.ts >= state.start && res[i].state.ts <= state.end) { + res.splice(i, 1); + } + } + else if (state.start) { + if (res[i].state.ts >= state.start) { + res.splice(i, 1); + } + } + else if (state.end) { + if (res[i].state.ts <= state.end) { + res.splice(i, 1); + } + } + else if (res[i].state.ts === state.ts) { + res.splice(i, 1); + found = true; + break; + } + } + } + } + } + if (!found) { + this.prepareTaskCheckTypeAndDbId(id, state, false, err => { + if (err) { + return cb?.(err); + } + const type = this.sqlDPs[id].type; + let query; + if (state.start && state.end) { + query = this.sqlFuncs.deleteFromTable(this.config.dbname, dbNames[type], this.sqlDPs[id].index, state.start, state.end); + } + else if (state.ts) { + query = this.sqlFuncs.deleteFromTable(this.config.dbname, dbNames[type], this.sqlDPs[id].index, state.ts); + } + else { + // delete all entries for ID + query = this.sqlFuncs.deleteFromTable(this.config.dbname, dbNames[type], this.sqlDPs[id].index); + } + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + cb?.(new Error(error)); + } + else { + this.tasks.push({ operation: 'query', query, id, callback: cb }); + this.tasks.length === 1 && this.processTasks(); + } + } + else { + this._executeQuery(query, id, cb); + } + }); + } + else { + cb?.(); + } + } + updateState(msg) { + if (!msg.message) { + this.log.error('updateState called with invalid data'); + return this.sendTo(msg.from, msg.command, { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, msg.callback); + } + let id; + if (Array.isArray(msg.message)) { + this.log.debug(`updateState ${msg.message.length} items`); + for (let i = 0; i < msg.message.length; i++) { + id = this.aliasMap[msg.message[i].id] ? this.aliasMap[msg.message[i].id] : msg.message[i].id; + if (msg.message[i].state && typeof msg.message[i].state === 'object') { + this.update(id, msg.message[i].state); + } + else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message[i])}`); + } + } + } + else if (msg.message.state && Array.isArray(msg.message.state)) { + this.log.debug(`updateState ${msg.message.state.length} items`); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + for (let j = 0; j < msg.message.state.length; j++) { + if (msg.message.state[j] && typeof msg.message.state[j] === 'object') { + this.update(id, msg.message.state[j]); + } + else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message.state[j])}`); + } + } + } + else if (msg.message.id && msg.message.state && typeof msg.message.state === 'object') { + this.log.debug('updateState 1 item'); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + return this.update(id, msg.message.state, () => this.sendTo(msg.from, msg.command, { + success: true, + sqlConnected: !!this.clientPool, + }, msg.callback)); + } + else { + this.log.error('updateState called with invalid data'); + return this.sendTo(msg.from, msg.command, { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, msg.callback); + } + this.sendTo(msg.from, msg.command, { + success: true, + sqlConnected: !!this.clientPool, + }, msg.callback); + } + deleteHistoryEntry(msg) { + if (!msg.message) { + this.log.error('deleteHistoryEntry called with invalid data'); + return this.sendTo(msg.from, msg.command, { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, msg.callback); + } + let id; + if (Array.isArray(msg.message)) { + this.log.debug(`deleteHistoryEntry ${msg.message.length} items`); + for (let i = 0; i < msg.message.length; i++) { + id = this.aliasMap[msg.message[i].id] ? this.aliasMap[msg.message[i].id] : msg.message[i].id; + // {id: 'blabla', ts: 892} + if (msg.message[i].ts) { + this.#delete(id, { ts: msg.message[i].ts }); + } + else if (msg.message[i].start) { + if (typeof msg.message[i].start === 'string') { + msg.message[i].start = new Date(msg.message[i].start).getTime(); + } + if (typeof msg.message[i].end === 'string') { + msg.message[i].end = new Date(msg.message[i].end).getTime(); + } + this.#delete(id, { start: msg.message[i].start, end: msg.message[i].end || Date.now() }); + } + else if (typeof msg.message[i].state === 'object' && + msg.message[i].state && + msg.message[i].state.ts) { + this.#delete(id, { ts: msg.message[i].state.ts }); + } + else if (typeof msg.message[i].state === 'object' && + msg.message[i].state && + msg.message[i].state.start) { + if (typeof msg.message[i].state.start === 'string') { + msg.message[i].state.start = new Date(msg.message[i].state.start).getTime(); + } + if (typeof msg.message[i].state.end === 'string') { + msg.message[i].state.end = new Date(msg.message[i].state.end).getTime(); + } + this.#delete(id, { + start: msg.message[i].state.start, + end: msg.message[i].state.end || Date.now(), + }); + } + else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message[i])}`); + } + } + } + else if (msg.message.state && Array.isArray(msg.message.state)) { + this.log.debug(`deleteHistoryEntry ${msg.message.state.length} items`); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + for (let j = 0; j < msg.message.state.length; j++) { + if (msg.message.state[j] && typeof msg.message.state[j] === 'object') { + if (msg.message.state[j].ts) { + this.#delete(id, { ts: msg.message.state[j].ts }); + } + else if (msg.message.state[j].start) { + if (typeof msg.message.state[j].start === 'string') { + msg.message.state[j].start = new Date(msg.message.state[j].start).getTime(); + } + if (typeof msg.message.state[j].end === 'string') { + msg.message.state[j].end = new Date(msg.message.state[j].end).getTime(); + } + this.#delete(id, { + start: msg.message.state[j].start, + end: msg.message.state[j].end || Date.now(), + }); + } + } + else if (msg.message.state[j] && typeof msg.message.state[j] === 'number') { + this.#delete(id, { ts: msg.message.state[j] }); + } + else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message.state[j])}`); + } + } + } + else if (msg.message.ts && Array.isArray(msg.message.ts)) { + this.log.debug(`deleteHistoryEntry ${msg.message.ts.length} items`); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + for (let j = 0; j < msg.message.ts.length; j++) { + if (msg.message.ts[j] && typeof msg.message.ts[j] === 'number') { + this.#delete(id, { ts: msg.message.ts[j] }); + } + else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message.ts[j])}`); + } + } + } + else if (msg.message.id && msg.message.state && typeof msg.message.state === 'object') { + this.log.debug('deleteHistoryEntry 1 item'); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + return this.#delete(id, { ts: msg.message.state.ts }, () => this.sendTo(msg.from, msg.command, { + success: true, + sqlConnected: !!this.clientPool, + }, msg.callback)); + } + else if (msg.message.id && msg.message.ts && typeof msg.message.ts === 'number') { + this.log.debug('deleteHistoryEntry 1 item'); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + return this.#delete(id, { ts: msg.message.ts }, () => this.sendTo(msg.from, msg.command, { + success: true, + sqlConnected: !!this.clientPool, + }, msg.callback)); + } + else { + this.log.error('deleteHistoryEntry called with invalid data'); + return this.sendTo(msg.from, msg.command, { error: `Invalid call: ${JSON.stringify(msg)}` }, msg.callback); + } + this.sendTo(msg.from, msg.command, { + success: true, + sqlConnected: !!this.clientPool, + }, msg.callback); + } + deleteStateAll(msg) { + if (!msg.message) { + this.log.error('deleteStateAll called with invalid data'); + return this.sendTo(msg.from, msg.command, { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, msg.callback); + } + let id; + if (Array.isArray(msg.message)) { + this.log.debug(`deleteStateAll ${msg.message.length} items`); + for (let i = 0; i < msg.message.length; i++) { + id = this.aliasMap[msg.message[i].id] ? this.aliasMap[msg.message[i].id] : msg.message[i].id; + this.#delete(id, {}); + } + } + else if (msg.message.id) { + this.log.debug('deleteStateAll 1 item'); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + return this.#delete(id, {}, () => this.sendTo(msg.from, msg.command, { + success: true, + sqlConnected: !!this.clientPool, + }, msg.callback)); + } + else { + this.log.error('deleteStateAll called with invalid data'); + return this.sendTo(msg.from, msg.command, { error: `Invalid call: ${JSON.stringify(msg)}` }, msg.callback); + } + this.sendTo(msg.from, msg.command, { + success: true, + sqlConnected: !!this.clientPool, + }, msg.callback); + } + storeStatePushData(id, state, applyRules) { + if (!state || typeof state !== 'object') { + throw new Error(`State ${JSON.stringify(state)} for ${id} is not valid`); + } + if (!this.sqlDPs[id]?.config) { + if (applyRules) { + throw new Error(`sql not enabled for ${id}, so can not apply the rules as requested`); + } + this.sqlDPs[id] ||= {}; + this.sqlDPs[id].realId = id; + } + return new Promise((resolve, reject) => { + if (applyRules) { + this.pushHistory(id, state); + resolve(true); + } + else { + this.pushHelper(id, state, err => { + if (err) { + reject(new Error(`Error writing state for ${id}: ${err.message}, Data: ${JSON.stringify(state)}`)); + } + else { + resolve(true); + } + }); + } + }); + } + async storeState(msg) { + if (!msg.message) { + this.log.error('storeState called with invalid data'); + return this.sendTo(msg.from, msg.command, { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, msg.callback); + } + const errors = []; + let successCount = 0; + if (Array.isArray(msg.message)) { + this.log.debug(`storeState ${msg.message.length} items`); + for (let i = 0; i < msg.message.length; i++) { + const id = this.aliasMap[msg.message[i].id] ? this.aliasMap[msg.message[i].id] : msg.message[i].id; + try { + await this.storeStatePushData(id, msg.message[i].state, msg.message[i].rules); + successCount++; + } + catch (err) { + errors.push(err.message); + } + } + } + else if (msg.message.id && Array.isArray(msg.message.state)) { + this.log.debug(`storeState ${msg.message.state.length} items`); + const id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + for (let j = 0; j < msg.message.state.length; j++) { + try { + await this.storeStatePushData(id, msg.message.state[j], msg.message.rules); + successCount++; + } + catch (err) { + errors.push(err.message); + } + } + } + else if (msg.message.id && msg.message.state) { + this.log.debug('storeState 1 item'); + const id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + try { + await this.storeStatePushData(id, msg.message.state, msg.message.rules); + successCount++; + } + catch (err) { + errors.push(err.message); + } + } + else { + this.log.error('storeState called with invalid data'); + return this.sendTo(msg.from, msg.command, { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, msg.callback); + } + if (errors.length) { + this.log.warn(`storeState executed with ${errors.length} errors: ${errors.join(', ')}`); + return this.sendTo(msg.from, msg.command, { + error: `${errors.length} errors happened while storing data`, + errors: errors, + successCount, + }, msg.callback); + } + this.log.debug(`storeState executed with ${successCount} states successfully`); + this.sendTo(msg.from, msg.command, { + success: true, + successCount, + sqlConnected: !!this.clientPool, + }, msg.callback); + } + getDpOverview(msg) { + const result = []; + const query = this.sqlFuncs.getIdSelect(this.config.dbname); + this.log.info(query); + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + return this.sendTo(msg.from, msg.command, { + error: `Cannot select ${query}: ${err || 'no client'}`, + }, msg.callback); + } + client.execute(query, (err, rows /* , fields */) => { + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot select ${query}: ${err}`); + this.sendTo(msg.from, msg.command, { + error: `Cannot select ${query}: ${err}`, + }, msg.callback); + return; + } + this.log.info(`Query result ${JSON.stringify(rows)}`); + if (rows?.length) { + for (let r = 0; r < rows.length; r++) { + result[rows[r].type] ||= {}; + result[rows[r].type][rows[r].id] = { name: rows[r].name }; + switch (dbNames[rows[r].type]) { + case 'ts_number': + result[rows[r].type][rows[r].id].type = 'number'; + break; + case 'ts_string': + result[rows[r].type][rows[r].id].type = 'string'; + break; + case 'ts_bool': + result[rows[r].type][rows[r].id].type = 'boolean'; + break; + } + } + this.log.info(`initialisation result: ${JSON.stringify(result)}`); + this.getFirstTsForIds(client, 0, result, msg); + } + }); + }); + } + getFirstTsForIds(client, typeId, resultData, msg) { + if (typeId < dbNames.length) { + if (!resultData[typeId]) { + this.getFirstTsForIds(client, (typeId + 1), resultData, msg); + } + else { + const query = this.sqlFuncs.getFirstTs(this.config.dbname, dbNames[typeId]); + this.log.info(query); + client.execute(query, (err, rows /* , fields */) => { + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot select ${query}: ${err}`); + this.sendTo(msg.from, msg.command, { + error: `Cannot select ${query}: ${err}`, + }, msg.callback); + return; + } + this.log.info(`Query result ${JSON.stringify(rows)}`); + if (rows?.length) { + for (let r = 0; r < rows.length; r++) { + if (resultData[typeId][rows[r].id]) { + resultData[typeId][rows[r].id].ts = rows[r].ts; + } + } + } + this.log.info(`enhanced result (${typeId}): ${JSON.stringify(resultData)}`); + this.dpOverviewTimeout = setTimeout((_client, typeId, resultData, msg) => { + this.dpOverviewTimeout = null; + this.getFirstTsForIds(_client, typeId, resultData, msg); + }, 5000, client, typeId + 1, resultData, msg); + }); + } + } + else { + this.returnClientToPool(client); + this.log.info('consolidate data ...'); + const result = {}; + for (let ti = 0; ti < dbNames.length; ti++) { + if (resultData[ti]) { + for (const index in resultData[ti]) { + if (!Object.prototype.hasOwnProperty.call(resultData[ti], index)) { + continue; + } + const id = resultData[ti][index].name; + if (!result[id]) { + result[id] = { + type: resultData[ti][index].type || 'undefined', + ts: resultData[ti][index].ts, + }; + } + else { + result[id].type = 'undefined'; + if (result[id].ts !== undefined && + (resultData[ti][index].ts === undefined || resultData[ti][index].ts < result[id].ts)) { + result[id].ts = resultData[ti][index].ts; + } + } + } + } + } + this.log.info(`Result: ${JSON.stringify(result)}`); + this.sendTo(msg.from, msg.command, { + success: true, + result: result, + }, msg.callback); + } + } + enableHistory(msg) { + if (!msg.message?.id) { + this.log.error('enableHistory called with invalid data'); + this.sendTo(msg.from, msg.command, { error: 'Invalid call' }, msg.callback); + return; + } + const obj = {}; + obj.common = {}; + obj.common.custom = {}; + obj.common.custom[this.namespace] = msg.message.options || {}; + obj.common.custom[this.namespace].enabled = true; + this.extendForeignObject(msg.message.id, obj, err => { + if (err) { + this.log.error(`enableHistory: ${err}`); + this.sendTo(msg.from, msg.command, { + error: err, + }, msg.callback); + } + else { + this.log.info(JSON.stringify(obj)); + this.sendTo(msg.from, msg.command, { success: true }, msg.callback); + } + }); + } + disableHistory(msg) { + if (!msg.message?.id) { + this.log.error('disableHistory called with invalid data'); + return this.sendTo(msg.from, msg.command, { + error: 'Invalid call', + }, msg.callback); + } + const obj = {}; + obj.common = {}; + obj.common.custom = { + [this.namespace]: { + enabled: false, + }, + }; + this.extendForeignObject(msg.message.id, obj, err => { + if (err) { + this.log.error(`disableHistory: ${err}`); + this.sendTo(msg.from, msg.command, { + error: err, + }, msg.callback); + } + else { + this.log.info(JSON.stringify(obj)); + this.sendTo(msg.from, msg.command, { success: true }, msg.callback); + } + }); + } + getEnabledDPs(msg) { + const data = {}; + for (const id in this.sqlDPs) { + if (Object.prototype.hasOwnProperty.call(this.sqlDPs, id) && this.sqlDPs[id]?.config?.enabled) { + data[this.sqlDPs[id].realId] = this.sqlDPs[id].config; + } + } + this.sendTo(msg.from, msg.command, data, msg.callback); + } + getDockerConfigMySQL(config) { + config.dockerMysql ||= { + enabled: false, + }; + config.dbtype = 'mysql'; + config.dbname = 'iobroker'; + config.user = 'iobroker'; + config.password = 'iobroker'; + config.dockerMysql.port = parseInt(config.dockerMysql.port || '3306', 10) || 3306; + config.port = config.dockerMysql.port; + config.multiRequests = true; + config.maxConnections = 100; + return { + iobEnabled: true, + iobStopOnUnload: config.dockerMysql.stopIfInstanceStopped || false, + removeOnExit: true, + // influxdb image: https://hub.docker.com/_/influxdb. Only version 2 is supported + image: 'mysql:lts', + ports: [ + { + hostPort: config.dockerMysql.port, + containerPort: 3306, + hostIP: config.dockerMysql.bind || '127.0.0.1', // only localhost to disable authentication and https safely + }, + ], + mounts: [ + { + source: 'mysql_data', + target: '/var/lib/mysql', + type: 'volume', + iobBackup: true, + }, + { + source: 'mysql_config', + target: '/etc/mysql/conf.d', + type: 'volume', + }, + ], + networkMode: true, // take default name iob_influxdb_ + // influxdb v2 requires some environment variables to be set on first start + environment: { + MYSQL_ROOT_PASSWORD: config.dockerMysql.rootPassword || 'root_iobroker', + MYSQL_DATABASE: 'iobroker', + MYSQL_USER: 'iobroker', + MYSQL_PASSWORD: 'iobroker', + MYSQL_ALLOW_EMPTY_PASSWORD: 'false', + }, + }; + } + normalizeAdapterConfig(config) { + config.dbname ||= 'iobroker'; + if (config.writeNulls === undefined) { + config.writeNulls = true; + } + config.retention = parseInt(config.retention, 10) || 0; + if (config.retention === -1) { + // Custom timeframe + config.retention = (parseInt(config.customRetentionDuration, 10) || 0) * 24 * 60 * 60; + } + config.debounce = parseInt(config.debounce, 10) || 0; + config.requestInterval = + config.requestInterval === undefined || config.requestInterval === null || config.requestInterval === '' + ? 0 + : parseInt(config.requestInterval, 10) || 0; + if (config.changesRelogInterval !== null && config.changesRelogInterval !== undefined) { + config.changesRelogInterval = parseInt(config.changesRelogInterval, 10); + } + else { + config.changesRelogInterval = 0; + } + if (!clients[config.dbtype]) { + this.log.error(`Unknown DB type: ${config.dbtype}`); + void this.stop?.(); + } + if (config.multiRequests !== undefined && config.dbtype !== 'sqlite') { + clients[config.dbtype].multiRequests = config.multiRequests; + } + if (config.maxConnections !== undefined && config.dbtype !== 'sqlite') { + config.maxConnections = parseInt(config.maxConnections, 10); + if (config.maxConnections !== 0 && !config.maxConnections) { + config.maxConnections = 100; + } + } + else { + config.maxConnections = 1; // SQLite does not support multiple connections + } + if (config.changesMinDelta !== null && config.changesMinDelta !== undefined) { + config.changesMinDelta = parseFloat(config.changesMinDelta.toString().replace(/,/g, '.')); + } + else { + config.changesMinDelta = 0; + } + if (config.blockTime !== null && config.blockTime !== undefined) { + config.blockTime = parseInt(config.blockTime, 10) || 0; + } + else { + if (config.debounce !== null && config.debounce !== undefined) { + config.debounce = parseInt(config.debounce, 10) || 0; + } + else { + config.blockTime = 0; + } + } + if (config.debounceTime !== null && config.debounceTime !== undefined) { + config.debounceTime = parseInt(config.debounceTime, 10) || 0; + } + else { + config.debounceTime = 0; + } + if (config.maxLength !== null && config.maxLength !== undefined) { + config.maxLength = parseInt(config.maxLength, 10) || 0; + } + else { + config.maxLength = 0; + } + this.multiRequests = clients[config.dbtype].multiRequests; + if (!this.multiRequests) { + config.writeNulls = false; + } + config.port = parseInt(config.port, 10) || 0; + if (config.round !== null && config.round !== undefined && config.round !== '') { + config.round = parseInt(config.round, 10); + if (!isFinite(config.round) || config.round < 0) { + config.round = null; + this.log.info(`Invalid round value: ${config.round} - ignore, do not round values`); + } + else { + config.round = Math.pow(10, config.round); + } + } + else { + config.round = null; + } + return config; + } + async createUserInDocker() { + const mySQLOptions = { + host: this.config.host, // needed for PostgreSQL , MySQL + user: 'root', + password: this.config.dockerMysql.rootPassword || 'root_iobroker', + port: this.config.port || undefined, + ssl: this.config.encrypt + ? { + rejectUnauthorized: !!this.config.rejectUnauthorized, + } + : undefined, + }; + const client = new mysql_client_1.MySQL2Client(mySQLOptions); + await client.connectAsync(); + // Show all users + const exists = await client.executeAsync(`SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = 'iobroker') as "ex";`); + if (exists?.[0]?.ex !== 1) { + // create user + await client.executeAsync(`CREATE USER 'iobroker'@'%' IDENTIFIED BY 'iobroker';`); + await client.executeAsync(`GRANT ALL PRIVILEGES ON * . * TO 'iobroker'@'%';`); + await client.executeAsync(`FLUSH PRIVILEGES;`); + } + await client.disconnectAsync(); + } + async main() { + this.setConnected(false); + // set default history if not yet set + try { + const obj = await this.getForeignObjectAsync('system.config'); + if (obj?.common && !obj.common.defaultHistory) { + obj.common.defaultHistory = this.namespace; + await this.setForeignObjectAsync('system.config', obj); + this.log.info(`Set default history instance to "${this.namespace}"`); + } + } + catch (e) { + this.log.error(`Cannot get system config: ${e}`); + } + // Normalize adapter config + const config = this.normalizeAdapterConfig(this.config); + this.sqlFuncs = SQLFuncs[config.dbtype]; + // Start docker if configured + if (this.config.dockerMysql?.enabled) { + this.config.dbtype = 'mysql'; + this.config.dbname = 'iobroker'; + this.config.user = 'iobroker'; + this.config.password = 'iobroker'; + this.config.dockerMysql.port = parseInt(this.config.dockerMysql.port || '3306', 10) || 3306; + this.config.port = this.config.dockerMysql.port; + this.config.multiRequests = true; + this.config.maxConnections = 100; + if (this.config.dockerPhpMyAdmin) { + this.config.dockerPhpMyAdmin.port = + parseInt(this.config.dockerPhpMyAdmin.port || '8080', 10) || 8080; + } + // Check that the user 'iobroker' exists + await this.createUserInDocker(); + } + if (config.dbtype === 'sqlite' || this.config.host) { + this.connect(() => { + // read all custom settings + this.getObjectView('system', 'custom', {}, (err, doc) => { + let count = 0; + if (doc?.rows) { + for (let i = 0, l = doc.rows.length; i < l; i++) { + if (doc.rows[i].value?.[this.namespace]) { + let id = doc.rows[i].id; + const realId = id; + if (doc.rows[i].value[this.namespace] && doc.rows[i].value[this.namespace].aliasId) { + this.aliasMap[id] = doc.rows[i].value[this.namespace].aliasId; + this.log.debug(`Found Alias: ${id} --> ${this.aliasMap[id]}`); + id = this.aliasMap[id]; + } + let storedIndex = null; + let storedType = null; + if (this.sqlDPs[id] && this.sqlDPs[id].index !== undefined) { + storedIndex = this.sqlDPs[id].index; + } + if (this.sqlDPs[id] && this.sqlDPs[id].dbType !== undefined) { + storedType = this.sqlDPs[id].dbType; + } + const config = this.normalizeCustomConfig(doc.rows[i].value); + this.sqlDPs[id] ||= {}; + const sqlDP = this.sqlDPs[id]; + sqlDP.config = config; + if (storedIndex !== null) { + sqlDP.index = storedIndex; + } + if (storedType !== null) { + sqlDP.dbType = storedType; + } + if (!config || typeof config !== 'object' || config.enabled === false) { + delete this.sqlDPs[id]; + } + else { + count++; + this.log.info(`enabled logging of ${id}, Alias=${id !== realId}, ${count} points now activated`); + // relogTimeout + if (config.changesOnly && config.changesRelogInterval > 0) { + if (sqlDP.relogTimeout) { + clearTimeout(sqlDP.relogTimeout); + } + sqlDP.relogTimeout = setTimeout(_id => { + this.sqlDPs[_id].relogTimeout = null; + this.reLogHelper(_id); + }, sqlDP.config.changesRelogInterval * 500 * (1 + Math.random()), id); + } + sqlDP.realId = realId; + sqlDP.list ||= []; + sqlDP.inFlight ||= {}; + // randomize lastCheck to avoid all datapoints to be checked at the same timepoint + sqlDP.lastCheck = Date.now() - Math.floor(Math.random() * 21600000 /* 6 hours */); + } + } + } + } + if (this.config.writeNulls) { + this.writeNulls(); + } + if (count < 200) { + Object.keys(this.sqlDPs).forEach(id => this.sqlDPs[id]?.config && + !this.sqlDPs[id].realId && + this.log.warn(`No realID found for ${id}`)); + this.subscribeForeignStates(Object.keys(this.sqlDPs).filter(id => this.sqlDPs[id]?.config && this.sqlDPs[id].realId)); + } + else { + this.subscribeAll = true; + this.subscribeForeignStates('*'); + } + this.subscribeForeignObjects('*'); + this.log.debug('Initialization done'); + this.setConnected(true); + this.processStartValues(); + // store all buffered data every 10 minutes to not lost the data + this.bufferChecker = setInterval(() => this.storeCached(), 10 * 60000); + }); + }); + } + } +} +exports.SqlAdapter = SqlAdapter; +// If started as allInOne mode => return function to create instance +if (require.main !== module) { + // Export the constructor in compact mode + module.exports = (options) => new SqlAdapter(options); +} +else { + // otherwise start the instance directly + (() => new SqlAdapter())(); +} +//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/build/main.js.map b/build/main.js.map new file mode 100644 index 00000000..f87bb228 --- /dev/null +++ b/build/main.js.map @@ -0,0 +1 @@ +{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..7b3ded23 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,59 @@ +version: '3.9' + +services: + mysql: + image: mysql:lts + labels: + - 'iobEnabled=${config.dockerMysql.enabled:-true}' + - 'iobStopOnUnload=${config.dockerMysql.stopIfInstanceStopped:-false}' + - 'iobAutoImageUpdate=${config.dockerMysql.autoImageUpdate:-true}' + - 'iobBackup=mysql_data' + ports: + - '${config.dockerMysql.bind:-127.0.0.1}:${config_dockerMysql_port:-3306}:3306' + container_name: mysql + environment: + MYSQL_DATABASE: 'iobroker' + MYSQL_USER: 'iobroker' + MYSQL_PASSWORD: 'iobroker' + MYSQL_ALLOW_EMPTY_PASSWORD: 'false' + MYSQL_ROOT_PASSWORD: '${config.dockerMysql.rootPassword:-root_iobroker}' + volumes: + - mysql_data:/var/lib/mysql + - mysql_config:/etc/mysql/conf.d + networks: + - true + restart: unless-stopped + + phpMyAdmin: + image: phpmyadmin/latest + labels: + - 'iobEnabled=${config.dockerPhpMyAdmin.enabled:-true}' + - 'iobStopOnUnload=${config.dockerPhpMyAdmin.stopIfInstanceStopped:-true}' + - 'iobBackup=phpmyadmin_data' + container_name: phpmyadmin + depends_on: + - mysql + ports: + - '${config.dockerPhpMyAdmin.bind:-127.0.0.1}:${config_dockerPhpMyAdmin_port:-8080}:8080' + environment: + GF_SECURITY_ADMIN_PASSWORD: '${config.dockerPhpMyAdmin.adminSecurityPassword:-iobroker}' + GF_SERVER_ROOT_URL: '${config.dockerPhpMyAdmin.serverRootUrl:-}' + GF_INSTALL_PLUGINS: '${config.dockerPhpMyAdmin.plugins:-}' + GF_USERS_ALLOW_SIGN_UP: '${config.dockerPhpMyAdmin.usersAllowSignUp:-false}' + MYSQL_ROOT_PASSWORD: config.dockerMysql.rootPassword || 'root_iobroker', + MYSQL_USER: 'iobroker' + MYSQL_PASSWORD: 'iobroker' + PMA_ABSOLUTE_URI: '${config.dockerPhpMyAdmin.absoluteUri}' + PMA_PORT: '${config.dockerMysql.port}:-3306' + PMA_HOST: 'iob_sql_${instance}_mysql' + networks: + - true + restart: unless-stopped + +networks: + true: + driver: bridge + +volumes: + mysql_data: + mysql_config: diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..0772a335 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,34 @@ +// ioBroker eslint template configuration file for js and ts files +// Please note that esm or react based modules need additional modules loaded. +import config from '@iobroker/eslint-config'; + +export default [ + ...config, + + { + // specify files to exclude from linting here + ignores: [ + '*.test.js', + 'test/**/*.js', + '*.config.mjs', + 'build/**/*', + 'admin/build', + 'admin/words.js', + 'admin/admin.d.ts', + '**/adapter-config.d.ts', + + // these files need to be adapted in the future + 'admin/blockly.js', + ], + }, + + { + // disable temporary the rule 'jsdoc/require-param' and enable 'jsdoc/require-jsdoc' + rules: { + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-param': 'off', + + '@typescript-eslint/no-require-imports': 'off', + }, + }, +]; diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index a5d5acf8..00000000 --- a/gulpfile.js +++ /dev/null @@ -1,337 +0,0 @@ -/*! - * ioBroker gulpfile - * Date: 2019-01-28 - */ -'use strict'; - -const gulp = require('gulp'); -const fs = require('fs'); -const pkg = require('./package.json'); -const iopackage = require('./io-package.json'); -const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; -const fileName = 'words.js'; -const EMPTY = ''; -const translate = require('./lib/tools').translateText; -const languages = { - en: {}, - de: {}, - ru: {}, - pt: {}, - nl: {}, - fr: {}, - it: {}, - es: {}, - pl: {}, - 'zh-cn': {} -}; - -function lang2data(lang) { - let str ='{\n'; - let count = 0; - for (const w in lang) { - if (lang.hasOwnProperty(w)) { - count++; - const key = ' "' + w.replace(/"/g, '\\"') + '": '; - str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; - } - } - if (!count) { - return '{\n}'; - } else { - return str.substring(0, str.length - 2) + '\n}'; - } -} - -function readWordJs(src) { - try { - let words; - if (fs.existsSync(src + 'js/' + fileName)) { - words = fs.readFileSync(src + 'js/' + fileName).toString(); - } else { - words = fs.readFileSync(src + fileName).toString(); - } - words = words.substring(words.indexOf('{'), words.length); - words = words.substring(0, words.lastIndexOf(';')); - - const resultFunc = new Function('return ' + words + ';'); - - return resultFunc(); - } catch (e) { - return null; - } -} - -function padRight(text, totalLength) { - return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : ''); -} - -function writeWordJs(data, src) { - let text = ''; - text += '/*global systemDictionary:true */\n'; - text += "'use strict';\n\n"; - text += 'systemDictionary = {\n'; - for (const word in data) { - if (data.hasOwnProperty(word)) { - text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); - let line = ''; - for (const lang in data[word]) { - if (data[word].hasOwnProperty(lang)) { - line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' '; - } - } - if (line) { - line = line.trim(); - line = line.substring(0, line.length - 1); - } - text += line + '},\n'; - } - } - text += '};'; - if (fs.existsSync(src + 'js/' + fileName)) { - fs.writeFileSync(src + 'js/' + fileName, text); - } else { - fs.writeFileSync(src + '' + fileName, text); - } -} - -function words2languages(src) { - const langs = Object.assign({}, languages); - const data = readWordJs(src); - if (data) { - for (const word in data) { - if (data.hasOwnProperty(word)) { - for (const lang in data[word]) { - if (data[word].hasOwnProperty(lang)) { - langs[lang][word] = data[word][lang]; - // pre-fill all other languages - for (const j in langs) { - if (langs.hasOwnProperty(j)) { - langs[j][word] = langs[j][word] || EMPTY; - } - } - } - } - } - } - if (!fs.existsSync(src + 'i18n/')) { - fs.mkdirSync(src + 'i18n/'); - } - for (const l in langs) { - if (!langs.hasOwnProperty(l)) - continue; - const keys = Object.keys(langs[l]); - keys.sort(); - const obj = {}; - for (let k = 0; k < keys.length; k++) { - obj[keys[k]] = langs[l][keys[k]]; - } - if (!fs.existsSync(src + 'i18n/' + l)) { - fs.mkdirSync(src + 'i18n/' + l); - } - - fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj)); - } - } else { - console.error('Cannot read or parse ' + fileName); - } -} - -function languages2words(src) { - const dirs = fs.readdirSync(src + 'i18n/'); - const langs = {}; - const bigOne = {}; - const order = Object.keys(languages); - dirs.sort(function (a, b) { - const posA = order.indexOf(a); - const posB = order.indexOf(b); - if (posA === -1 && posB === -1) { - if (a > b) - return 1; - if (a < b) - return -1; - return 0; - } else if (posA === -1) { - return -1; - } else if (posB === -1) { - return 1; - } else { - if (posA > posB) - return 1; - if (posA < posB) - return -1; - return 0; - } - }); - for (const lang of dirs) { - if (lang === 'flat.txt') - continue; - langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString(); - langs[lang] = JSON.parse(langs[lang]); - const words = langs[lang]; - for (const word in words) { - if (words.hasOwnProperty(word)) { - bigOne[word] = bigOne[word] || {}; - if (words[word] !== EMPTY) { - bigOne[word][lang] = words[word]; - } - } - } - } - // read actual words.js - const aWords = readWordJs(); - - const temporaryIgnore = ['flat.txt']; - if (aWords) { - // Merge words together - for (const w in aWords) { - if (aWords.hasOwnProperty(w)) { - if (!bigOne[w]) { - console.warn('Take from actual words.js: ' + w); - bigOne[w] = aWords[w]; - } - dirs.forEach(function (lang) { - if (temporaryIgnore.indexOf(lang) !== -1) - return; - if (!bigOne[w][lang]) { - console.warn('Missing "' + lang + '": ' + w); - } - }); - } - } - - } - - writeWordJs(bigOne, src); -} - -async function translateNotExisting(obj, baseText, yandex) { - let t = obj['en']; - if (!t) { - t = baseText; - } - - if (t) { - for (let l in languages) { - if (!obj[l]) { - const time = new Date().getTime(); - obj[l] = await translate(t, l, yandex); - console.log('en -> ' + l + ' ' + (new Date().getTime() - time) + ' ms'); - } - } - } -} - -//TASKS - -gulp.task('adminWords2languages', function (done) { - words2languages('./admin/'); - done(); -}); - -gulp.task('adminLanguages2words', function (done) { - languages2words('./admin/'); - done(); -}); - -gulp.task('updatePackages', function (done) { - iopackage.common.version = pkg.version; - iopackage.common.news = iopackage.common.news || {}; - if (!iopackage.common.news[pkg.version]) { - const news = iopackage.common.news; - const newNews = {}; - - newNews[pkg.version] = { - en: 'news', - de: 'neues', - ru: 'новое', - pt: 'novidades', - nl: 'nieuws', - fr: 'nouvelles', - it: 'notizie', - es: 'noticias', - pl: 'nowości', - 'zh-cn': '新' - }; - iopackage.common.news = Object.assign(newNews, news); - } - fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); - done(); -}); - -gulp.task('updateReadme', function (done) { - const readme = fs.readFileSync('README.md').toString(); - const pos = readme.indexOf('## Changelog\n'); - if (pos !== -1) { - const readmeStart = readme.substring(0, pos + '## Changelog\n'.length); - const readmeEnd = readme.substring(pos + '## Changelog\n'.length); - - if (readme.indexOf(version) === -1) { - const timestamp = new Date(); - const date = timestamp.getFullYear() + '-' + - ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + - ('0' + (timestamp.getDate()).toString(10)).slice(-2); - - let news = ''; - if (iopackage.common.news && iopackage.common.news[pkg.version]) { - news += '* ' + iopackage.common.news[pkg.version].en; - } - - fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd); - } - } - done(); -}); - -gulp.task('translate', async function (done) { - - let yandex; - const i = process.argv.indexOf('--yandex'); - if (i > -1) { - yandex = process.argv[i + 1]; - } - - if (iopackage && iopackage.common) { - if (iopackage.common.news) { - console.log('Translate News'); - for (let k in iopackage.common.news) { - console.log('News: ' + k); - let nw = iopackage.common.news[k]; - await translateNotExisting(nw, null, yandex); - } - } - if (iopackage.common.titleLang) { - console.log('Translate Title'); - await translateNotExisting(iopackage.common.titleLang, iopackage.common.title, yandex); - } - if (iopackage.common.desc) { - console.log('Translate Description'); - await translateNotExisting(iopackage.common.desc, null, yandex); - } - - if (fs.existsSync('./admin/i18n/en/translations.json')) { - let enTranslations = require('./admin/i18n/en/translations.json'); - for (let l in languages) { - console.log('Translate Text: ' + l); - let existing = {}; - if (fs.existsSync('./admin/i18n/' + l + '/translations.json')) { - existing = require('./admin/i18n/' + l + '/translations.json'); - } - for (let t in enTranslations) { - if (!existing[t]) { - existing[t] = await translate(enTranslations[t], l, yandex); - } - } - if (!fs.existsSync('./admin/i18n/' + l + '/')) { - fs.mkdirSync('./admin/i18n/' + l + '/'); - } - fs.writeFileSync('./admin/i18n/' + l + '/translations.json', JSON.stringify(existing, null, 4)); - } - } - - } - fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); -}); - -gulp.task('translateAndUpdateWordsJS', gulp.series('translate', 'adminLanguages2words', 'adminWords2languages')); - -gulp.task('default', gulp.series('updatePackages', 'updateReadme')); \ No newline at end of file diff --git a/io-package.json b/io-package.json index f6e2b01b..a5c34ecf 100644 --- a/io-package.json +++ b/io-package.json @@ -1,276 +1,282 @@ { - "common": { - "name": "sql", - "title": "SQL History", - "titleLang": { - "en": "SQL logging", - "de": "SQL-Protokollierung", - "ru": "Ведение журнала SQL", - "pt": "Log de SQL", - "nl": "SQL logging", - "fr": "Journalisation SQL", - "it": "Registrazione SQL", - "es": "Registro de SQL", - "pl": "Rejestrowanie SQL", - "zh-cn": "SQL记录" - }, - "desc": { - "en": "Logging of states into SQL DB", - "de": "Loggt die Historie von einzelnen Zuständen in einer SQL DB", - "ru": "Сохраняет историю событий для отдельных состояний в SQL DB", - "pt": "Registro de estados em SQL DB", - "nl": "Logging van toestanden in SQL DB", - "fr": "Journalisation des états dans la base de données SQL", - "it": "Registrazione degli stati nel DB SQL", - "es": "Registro de estados en SQL DB", - "pl": "Rejestrowanie stanów w SQL DB", - "zh-cn": "将状态记录到SQL DB" - }, - "version": "3.0.1", - "news": { - "3.0.1": { - "en": "(foxriver76) upgraded dependencies", - "de": "(foxriver76) verbesserte abhängigkeiten", - "ru": "(foxriver76) повышенные зависимости", - "pt": "(foxriver76) dependências atualizadas", - "nl": "(foxriver76) verbeterde afhankelijkheden", - "fr": "(foxriver76) dépendances améliorées", - "it": "(foxriver76) dipendenze aggiornate", - "es": "(foxriver76)", - "pl": "(foxriver76) zmodernizowane zależności", - "uk": "(foxriver76) оновлені залежності", - "zh-cn": "(foxriver76) 升级的依赖性" - }, - "3.0.0": { - "en": "IMPORTANT: Node.js 16.x is now needed at a minimum!\nAllowed setting port 0 as default\nChecked if a string is written into the number table", - "de": "WICHTIG: Node.js 16.x wird jetzt auf ein Minimum benötigt!\nErlaubte Einstellung Port 0 als Standard\nGeprüft, ob ein String in die Nummerntabelle geschrieben wird", - "ru": "ВАЖНО: Node.js 16.x теперь нужен минимум!\nДопустимый порт установки 0 как по умолчанию\nПроверено, если строка написана в таблице номера", - "pt": "IMPORTANTE: Node.js 16.x é agora necessário no mínimo!\nConfiguração permitida da porta 0 como padrão\nVerificado se uma string é escrita na tabela de números", - "nl": "De 16.x is nu nodig op een minimum!\nVertaling:\nControleer of er een touw in de tafel is geschreven", - "fr": "IMPORTANT: Node.js 16.x est maintenant nécessaire au minimum!\nRéglage autorisé port 0 par défaut\nVérifié si une chaîne est écrite dans la table numéro", - "it": "IMPORTANTE: Node.js 16.x è ora necessario al minimo!\nImpostare la porta 0 come predefinito\nControllato se una stringa è scritta nella tabella numerica", - "es": "IMPORTANTE: Node.js 16.x ahora es necesario como mínimo!\nPuerto de configuración 0 como predeterminado\nRevisado si una cadena está escrita en la tabla número", - "pl": "IMPORTANT: Node.js 16.x jest obecnie potrzebny przy minimalnej cenie!\nAllowed Port 0 jako domyślny\nUwaga, czy struna jest zapisywana w tabeli liczbowej", - "uk": "ВАЖЛИВО: Node.js 16.x тепер потрібен мінімум!\nУсього налаштування порту 0 як за замовчуванням\nПеревірити, якщо рядок записано в номер таблиці", - "zh-cn": "IMPORTANT: Nodejs 16x现在至少需要!\n允许将港口建为零\n如果在表格编号上写一包,则标示" - }, - "2.2.0": { - "en": "IMPORTANT: Node.js 14.x is now needed at minimum!\nFix potential crash cases with upcoming js-controller versions", - "de": "WICHTIG: Node.js 14.x wird jetzt mindestens benötigt!\nBeheben Sie mögliche Crash-Fälle mit anstehenden js-Controller-Versionen", - "ru": "ВАЖНО: Node.js 14.x теперь нужен минимум!\nИсправление потенциальных кейсов с предстоящими версиями js-controller", - "pt": "IMPORTANTE: Node.js 14.x é agora necessário no mínimo!\nCorrigir casos potenciais de acidente com versões js-controller", - "nl": "Node is 14.x is nu nodig op minimum!\nVertaling:", - "fr": "IMPORTANT: Node.js 14.x est maintenant nécessaire au minimum!\nRéparer les cas de crash potentiels avec les versions de js-controller à venir", - "it": "IMPORTANTE: Node.js 14.x è ora necessario al minimo!\nRisolvere potenziali casi di crash con le prossime versioni js-controller", - "es": "IMPORTANTE: Node.js 14.x ahora es necesario al mínimo!\nFijar posibles casos de choque con las próximas versiones js-controller", - "pl": "IMPORTANT: Nodejs 14.x jest obecnie potrzebny na minimum!\nFix ma problemy z nadchodzącymi wersjami js-controllerowymi", - "zh-cn": "IMPORTANT: Nodejs 14x现在至少需要!\n九件潜在的事故,即将出版的丛书控制字" - }, - "2.1.8": { - "en": "Optimize getHistory query by using \"UNION ALL\"\nFix crash cases reported by Sentry", - "de": "Optimieren Sie Geschichte Anfrage mit \"UNION ALL\"\nReparieren Crashfälle gemeldet von Sentry", - "ru": "Оптимизируйте получите Запрос истории с помощью \"UNION ALL\"\nИсправление случаев аварии, описанных Sentry", - "pt": "Otimizar obter Consulta de história usando \"UNION ALL\"\nCorrigir casos de acidente relatados por Sentry", - "nl": "Optimaliseren Vertaling:\nVertaling:", - "fr": "Optimiser les résultats Demande d'histoire en utilisant \"UNION ALL\"\nTroubles de crash signalés par Sentry", - "it": "Ottimizzare ottenere query di storia utilizzando \"UNION ALL\"\nCorrezione dei casi di crash segnalati da Sentry", - "es": "Optimize get Consulta de historia usando \"UNION ALL\"\nCasos de accidente fijo reportados por Sentry", - "pl": "Optimizować History query by use the UNION ALL (ang.)\nSprawy katastrofy Fix podane przez Sentry’a", - "zh-cn": "A. 全球化 利用“联合国目标”的历史询问\nSentry所报告的六例事故" - }, - "2.1.7": { - "en": "Fix crash cases reported by Sentry", - "de": "Reparieren Crashfälle gemeldet von Sentry", - "ru": "Исправление случаев аварии, описанных Sentry", - "pt": "Corrigir casos de acidente relatados por Sentry", - "nl": "Vertaling:", - "fr": "Troubles de crash signalés par Sentry", - "it": "Correzione dei casi di crash segnalati da Sentry", - "es": "Casos de accidente fijo reportados por Sentry", - "pl": "Sprawy katastrofy Fix podane przez Sentry’a", - "zh-cn": "Sentry所报告的六例事故" - }, - "2.1.6": { - "en": "Allow to remove a configuration value for \"round\" in config again", - "de": "Lassen Sie einen Konfigurationswert für Runde in config wieder entfernen", - "ru": "Разрешить удалить конфигурационную ценность для раунда в config снова", - "pt": "Permitir remover um valor de configuração para round em config novamente", - "nl": "Sta toe om weer een configuratiewaarde te verwijderen voor ronde in het leger", - "fr": "Permettre de supprimer une valeur de configuration pour round in config à nouveau", - "it": "Permette di rimuovere un valore di configurazione per round in config di nuovo", - "es": "Permitir eliminar de nuevo un valor de configuración para redondear en configuración", - "pl": "Allow, aby usunąć wartość konfiguracyjna w zakręcie ponownie w konfigunie", - "zh-cn": "允许再次取消一轮谈判的组合价值" - }, - "2.1.5": { - "en": "When no count is provided for aggregate \"none\" or \"onchange\" then the limit (default 2000) is used as count to define the number of data to return.\nFix the initialization of types and IDs for some cases.", - "de": "Wenn keine Zählung für die Aggregate keine oder Änderung vorgesehen ist, wird die Grenze (Standard 2000) als Zählung verwendet, um die Anzahl der Daten zu definieren, um zurückzukehren.\nBeheben Sie die Initialisierung von Typen und IDs für einige Fälle.", - "ru": "Когда никакое число не предусмотрено для агрегировать никакое или онмен после этого предел (по умолчанию 2000) использован как считать для того чтобы определить количество данных для возвращения.\nИсправьте инициализацию типов и идентификаторов для некоторых случаев.", - "pt": "Quando nenhuma contagem é fornecida para agregar nenhum ou onchange, então o limite (padrão 2000) é usado como contagem para definir o número de dados para retornar.\nCorrigir a inicialização de tipos e IDs para alguns casos.", - "nl": "Als er geen tel is voor aggregate noch onchange dan wordt de limiet (default 2000) gebruikt om het aantal data te definiëren om terug te keren.\nMaak de initialisatie van types en ID's voor sommige gevallen.", - "fr": "Si aucun compte n'est fourni pour l'agrégat aucun ou le changement, la limite (par défaut 2000) est utilisée comme compte pour définir le nombre de données à retourner.\nFixer l'initialisation des types et des ID pour certains cas.", - "it": "Quando non è previsto alcun conteggio per aggregato nessuno o per il cambiamento, il limite (default 2000) viene utilizzato come conteggio per definire il numero di dati da restituire.\nFissare l'inizializzazione di tipi e ID per alcuni casi.", - "es": "Cuando no se proporciona ningún recuento para agregado ninguno o cambio entonces el límite (por defecto 2000) se utiliza como recuento para definir el número de datos para regresar.\nArreglar la inicialización de tipos e identificaciones para algunos casos.", - "pl": "Jeśli nie jest liczona dla agregacji ani zmian, to limit (default 2000) jest używany jako określenie liczby danych do powrotu.\nW niektórych przypadkach inicjacja typów i ID.", - "zh-cn": "当时没有对总数据或换算进行计算(2000年12月)用于确定返回的数据数量。.\n确定某些案例的类型和研发。." - } - }, - "mode": "daemon", - "platform": "Javascript/Node.js", - "loglevel": "info", - "messagebox": true, - "subscribe": "messagebox", - "keywords": [ - "charts", - "sql", - "logging", - "graphs", - "archive" - ], - "preserveSettings": "custom", - "supportCustoms": true, - "getHistory": true, - "enabled": true, - "stopBeforeUpdate": true, - "compact": true, - "authors": [ - "bluefox ", - "Apollon77 " - ], - "license": "MIT", - "readme": "https://github.com/ioBroker/ioBroker.sql/blob/master/README.md", - "icon": "sql.png", - "extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.sql/master/admin/sql.png", - "type": "storage", - "dependencies": [ - { - "js-controller": ">=3.3.0" - } - ], - "globalDependencies": [ - { - "admin": ">=5.1.28" - } - ], - "supportStopInstance": 10000, - "stopTimeout": 10000, - "plugins": { - "sentry": { - "dsn": "https://c30462d3e0834993b0aa29824346701e@sentry.iobroker.net/45" - } - }, - "adminUI": { - "config": "json", - "custom": "json" - }, - "tier": 1, - "eraseOnUpload": true, - "connectionType": "none", - "dataSource": "push", - "messages": [ - { - "condition": { - "operand": "and", - "rules": [ - "oldVersion<2.0.0", - "newVersion>=2.0.0" - ] + "common": { + "name": "sql", + "titleLang": { + "en": "SQL logging", + "de": "SQL-Protokollierung", + "ru": "Ведение журнала SQL", + "pt": "Log de SQL", + "nl": "SQL logging", + "fr": "Journalisation SQL", + "it": "Registrazione SQL", + "es": "Registro de SQL", + "pl": "Rejestrowanie SQL", + "zh-cn": "SQL记录" + }, + "desc": { + "en": "Logging of states into SQL DB", + "de": "Loggt die Historie von einzelnen Zuständen in einer SQL DB", + "ru": "Сохраняет историю событий для отдельных состояний в SQL DB", + "pt": "Registro de estados em SQL DB", + "nl": "Logging van toestanden in SQL DB", + "fr": "Journalisation des états dans la base de données SQL", + "it": "Registrazione degli stati nel DB SQL", + "es": "Registro de estados en SQL DB", + "pl": "Rejestrowanie stanów w SQL DB", + "zh-cn": "将状态记录到SQL DB" }, - "title": { - "en": "Important notice!", - "de": "Wichtiger Hinweis!", - "ru": "Важное замечание!", - "pt": "Notícia importante!", - "nl": "Belangrijke mededeling!", - "fr": "Avis important!", - "it": "Avviso IMPORTANTE!", - "es": "Noticia importante!", - "pl": "Ważna uwaga!", - "zh-cn": "重要通知!" + "version": "3.0.1", + "news": { + "3.0.1": { + "en": "(foxriver76) upgraded dependencies", + "de": "(foxriver76) verbesserte abhängigkeiten", + "ru": "(foxriver76) повышенные зависимости", + "pt": "(foxriver76) dependências atualizadas", + "nl": "(foxriver76) verbeterde afhankelijkheden", + "fr": "(foxriver76) dépendances améliorées", + "it": "(foxriver76) dipendenze aggiornate", + "es": "(foxriver76)", + "pl": "(foxriver76) zmodernizowane zależności", + "uk": "(foxriver76) оновлені залежності", + "zh-cn": "(foxriver76) 升级的依赖性" + }, + "3.0.0": { + "en": "IMPORTANT: Node.js 16.x is now needed at a minimum!\nAllowed setting port 0 as default\nChecked if a string is written into the number table", + "de": "WICHTIG: Node.js 16.x wird jetzt auf ein Minimum benötigt!\nErlaubte Einstellung Port 0 als Standard\nGeprüft, ob ein String in die Nummerntabelle geschrieben wird", + "ru": "ВАЖНО: Node.js 16.x теперь нужен минимум!\nДопустимый порт установки 0 как по умолчанию\nПроверено, если строка написана в таблице номера", + "pt": "IMPORTANTE: Node.js 16.x é agora necessário no mínimo!\nConfiguração permitida da porta 0 como padrão\nVerificado se uma string é escrita na tabela de números", + "nl": "De 16.x is nu nodig op een minimum!\nVertaling:\nControleer of er een touw in de tafel is geschreven", + "fr": "IMPORTANT: Node.js 16.x est maintenant nécessaire au minimum!\nRéglage autorisé port 0 par défaut\nVérifié si une chaîne est écrite dans la table numéro", + "it": "IMPORTANTE: Node.js 16.x è ora necessario al minimo!\nImpostare la porta 0 come predefinito\nControllato se una stringa è scritta nella tabella numerica", + "es": "IMPORTANTE: Node.js 16.x ahora es necesario como mínimo!\nPuerto de configuración 0 como predeterminado\nRevisado si una cadena está escrita en la tabla número", + "pl": "IMPORTANT: Node.js 16.x jest obecnie potrzebny przy minimalnej cenie!\nAllowed Port 0 jako domyślny\nUwaga, czy struna jest zapisywana w tabeli liczbowej", + "uk": "ВАЖЛИВО: Node.js 16.x тепер потрібен мінімум!\nУсього налаштування порту 0 як за замовчуванням\nПеревірити, якщо рядок записано в номер таблиці", + "zh-cn": "IMPORTANT: Nodejs 16x现在至少需要!\n允许将港口建为零\n如果在表格编号上写一包,则标示" + }, + "2.2.0": { + "en": "IMPORTANT: Node.js 14.x is now needed at minimum!\nFix potential crash cases with upcoming js-controller versions", + "de": "WICHTIG: Node.js 14.x wird jetzt mindestens benötigt!\nBeheben Sie mögliche Crash-Fälle mit anstehenden js-Controller-Versionen", + "ru": "ВАЖНО: Node.js 14.x теперь нужен минимум!\nИсправление потенциальных кейсов с предстоящими версиями js-controller", + "pt": "IMPORTANTE: Node.js 14.x é agora necessário no mínimo!\nCorrigir casos potenciais de acidente com versões js-controller", + "nl": "Node is 14.x is nu nodig op minimum!\nVertaling:", + "fr": "IMPORTANT: Node.js 14.x est maintenant nécessaire au minimum!\nRéparer les cas de crash potentiels avec les versions de js-controller à venir", + "it": "IMPORTANTE: Node.js 14.x è ora necessario al minimo!\nRisolvere potenziali casi di crash con le prossime versioni js-controller", + "es": "IMPORTANTE: Node.js 14.x ahora es necesario al mínimo!\nFijar posibles casos de choque con las próximas versiones js-controller", + "pl": "IMPORTANT: Nodejs 14.x jest obecnie potrzebny na minimum!\nFix ma problemy z nadchodzącymi wersjami js-controllerowymi", + "zh-cn": "IMPORTANT: Nodejs 14x现在至少需要!\n九件潜在的事故,即将出版的丛书控制字" + }, + "2.1.8": { + "en": "Optimize getHistory query by using \"UNION ALL\"\nFix crash cases reported by Sentry", + "de": "Optimieren Sie Geschichte Anfrage mit \"UNION ALL\"\nReparieren Crashfälle gemeldet von Sentry", + "ru": "Оптимизируйте получите Запрос истории с помощью \"UNION ALL\"\nИсправление случаев аварии, описанных Sentry", + "pt": "Otimizar obter Consulta de história usando \"UNION ALL\"\nCorrigir casos de acidente relatados por Sentry", + "nl": "Optimaliseren Vertaling:\nVertaling:", + "fr": "Optimiser les résultats Demande d'histoire en utilisant \"UNION ALL\"\nTroubles de crash signalés par Sentry", + "it": "Ottimizzare ottenere query di storia utilizzando \"UNION ALL\"\nCorrezione dei casi di crash segnalati da Sentry", + "es": "Optimize get Consulta de historia usando \"UNION ALL\"\nCasos de accidente fijo reportados por Sentry", + "pl": "Optimizować History query by use the UNION ALL (ang.)\nSprawy katastrofy Fix podane przez Sentry’a", + "zh-cn": "A. 全球化 利用“联合国目标”的历史询问\nSentry所报告的六例事故" + }, + "2.1.7": { + "en": "Fix crash cases reported by Sentry", + "de": "Reparieren Crashfälle gemeldet von Sentry", + "ru": "Исправление случаев аварии, описанных Sentry", + "pt": "Corrigir casos de acidente relatados por Sentry", + "nl": "Vertaling:", + "fr": "Troubles de crash signalés par Sentry", + "it": "Correzione dei casi di crash segnalati da Sentry", + "es": "Casos de accidente fijo reportados por Sentry", + "pl": "Sprawy katastrofy Fix podane przez Sentry’a", + "zh-cn": "Sentry所报告的六例事故" + }, + "2.1.6": { + "en": "Allow to remove a configuration value for \"round\" in config again", + "de": "Lassen Sie einen Konfigurationswert für Runde in config wieder entfernen", + "ru": "Разрешить удалить конфигурационную ценность для раунда в config снова", + "pt": "Permitir remover um valor de configuração para round em config novamente", + "nl": "Sta toe om weer een configuratiewaarde te verwijderen voor ronde in het leger", + "fr": "Permettre de supprimer une valeur de configuration pour round in config à nouveau", + "it": "Permette di rimuovere un valore di configurazione per round in config di nuovo", + "es": "Permitir eliminar de nuevo un valor de configuración para redondear en configuración", + "pl": "Allow, aby usunąć wartość konfiguracyjna w zakręcie ponownie w konfigunie", + "zh-cn": "允许再次取消一轮谈判的组合价值" + }, + "2.1.5": { + "en": "When no count is provided for aggregate \"none\" or \"onchange\" then the limit (default 2000) is used as count to define the number of data to return.\nFix the initialization of types and IDs for some cases.", + "de": "Wenn keine Zählung für die Aggregate keine oder Änderung vorgesehen ist, wird die Grenze (Standard 2000) als Zählung verwendet, um die Anzahl der Daten zu definieren, um zurückzukehren.\nBeheben Sie die Initialisierung von Typen und IDs für einige Fälle.", + "ru": "Когда никакое число не предусмотрено для агрегировать никакое или онмен после этого предел (по умолчанию 2000) использован как считать для того чтобы определить количество данных для возвращения.\nИсправьте инициализацию типов и идентификаторов для некоторых случаев.", + "pt": "Quando nenhuma contagem é fornecida para agregar nenhum ou onchange, então o limite (padrão 2000) é usado como contagem para definir o número de dados para retornar.\nCorrigir a inicialização de tipos e IDs para alguns casos.", + "nl": "Als er geen tel is voor aggregate noch onchange dan wordt de limiet (default 2000) gebruikt om het aantal data te definiëren om terug te keren.\nMaak de initialisatie van types en ID's voor sommige gevallen.", + "fr": "Si aucun compte n'est fourni pour l'agrégat aucun ou le changement, la limite (par défaut 2000) est utilisée comme compte pour définir le nombre de données à retourner.\nFixer l'initialisation des types et des ID pour certains cas.", + "it": "Quando non è previsto alcun conteggio per aggregato nessuno o per il cambiamento, il limite (default 2000) viene utilizzato come conteggio per definire il numero di dati da restituire.\nFissare l'inizializzazione di tipi e ID per alcuni casi.", + "es": "Cuando no se proporciona ningún recuento para agregado ninguno o cambio entonces el límite (por defecto 2000) se utiliza como recuento para definir el número de datos para regresar.\nArreglar la inicialización de tipos e identificaciones para algunos casos.", + "pl": "Jeśli nie jest liczona dla agregacji ani zmian, to limit (default 2000) jest używany jako określenie liczby danych do powrotu.\nW niektórych przypadkach inicjacja typów i ID.", + "zh-cn": "当时没有对总数据或换算进行计算(2000年12月)用于确定返回的数据数量。.\n确定某些案例的类型和研发。." + } }, - "text": { - "en": "This new version introduces several changes how values are logged because we fixed several issues and added new features. Please especially check the defined \"debounce\" and \"block time\" settings in your datapoints if values are not logged as expected and make sure the settings make sense. For more details please refer to the changelog and Readme.", - "de": "Diese neue Version führt mehrere Änderungen ein, wie Werte protokolliert werden, da wir mehrere Probleme behoben und neue Funktionen hinzugefügt haben. Bitte überprüfen Sie insbesondere die definierten „Entprellen“- und „Sperrzeit“-Einstellungen in Ihren Datenpunkten, wenn Werte nicht wie erwartet protokolliert werden, und stellen Sie sicher, dass die Einstellungen sinnvoll sind. Weitere Details finden Sie im Changelog und in der Readme.", - "ru": "В этой новой версии внесено несколько изменений в регистрацию значений, поскольку мы исправили несколько проблем и добавили новые функции. Пожалуйста, особенно проверьте определенные настройки «debounce» и «block time» в ваших точках данных, если значения не регистрируются должным образом, и убедитесь, что настройки имеют смысл. Для получения более подробной информации, пожалуйста, обратитесь к журналу изменений и Readme.", - "pt": "Esta nova versão apresenta várias alterações na forma como os valores são registrados, pois corrigimos vários problemas e adicionamos novos recursos. Verifique especialmente as configurações definidas de \"debounce\" e \"block time\" em seus pontos de dados se os valores não forem registrados conforme o esperado e verifique se as configurações fazem sentido. Para obter mais detalhes, consulte o log de alterações e o Leiame.", - "nl": "Deze nieuwe versie introduceert verschillende wijzigingen in de manier waarop waarden worden geregistreerd, omdat we verschillende problemen hebben opgelost en nieuwe functies hebben toegevoegd. Controleer vooral de gedefinieerde \"debounce\" en \"block time\" instellingen in uw datapunten als de waarden niet zijn vastgelegd zoals verwacht en zorg ervoor dat de instellingen kloppen. Raadpleeg de changelog en Readme voor meer informatie.", - "fr": "Cette nouvelle version introduit plusieurs changements dans la journalisation des valeurs car nous avons corrigé plusieurs problèmes et ajouté de nouvelles fonctionnalités. Veuillez vérifier en particulier les paramètres \"anti-rebond\" et \"temps de blocage\" définis dans vos points de données si les valeurs ne sont pas enregistrées comme prévu et assurez-vous que les paramètres ont un sens. Pour plus de détails, veuillez consulter le journal des modifications et le fichier Lisez-moi.", - "it": "Questa nuova versione introduce diverse modifiche al modo in cui i valori vengono registrati perché abbiamo risolto diversi problemi e aggiunto nuove funzionalità. Controllare in particolare le impostazioni definite \"debounce\" e \"block time\" nei propri datapoint se i valori non sono registrati come previsto e assicurarsi che le impostazioni abbiano un senso. Per maggiori dettagli, fare riferimento al log delle modifiche e al Leggimi.", - "es": "Esta nueva versión introduce varios cambios en la forma en que se registran los valores porque solucionamos varios problemas y agregamos nuevas funciones. Verifique especialmente la configuración definida de \"antirrebote\" y \"tiempo de bloqueo\" en sus puntos de datos si los valores no se registran como se esperaba y asegúrese de que la configuración tenga sentido. Para obtener más detalles, consulte el registro de cambios y el archivo Léame.", - "pl": "Ta nowa wersja wprowadza kilka zmian w sposobie rejestrowania wartości, ponieważ naprawiliśmy kilka problemów i dodaliśmy nowe funkcje. W szczególności sprawdź zdefiniowane ustawienia „odbicia” i „czasu blokowania” w punktach danych, jeśli wartości nie są rejestrowane zgodnie z oczekiwaniami i upewnij się, że ustawienia mają sens. Aby uzyskać więcej informacji, zapoznaj się z dziennikiem zmian i Readme.", - "zh-cn": "这个新版本引入了一些更改值的记录方式,因为我们修复了几个问题并添加了新功能。如果未按预期记录值,请特别检查数据点中定义的“去抖动”和“阻塞时间”设置,并确保设置有意义。有关更多详细信息,请参阅更改日志和自述文件。" + "mode": "daemon", + "platform": "Javascript/Node.js", + "loglevel": "info", + "messagebox": true, + "subscribe": "messagebox", + "keywords": ["charts", "sql", "logging", "graphs", "archive"], + "preserveSettings": "custom", + "supportCustoms": true, + "getHistory": true, + "enabled": true, + "stopBeforeUpdate": true, + "compact": true, + "authors": ["bluefox ", "Apollon77 "], + "license": "MIT", + "readme": "https://github.com/ioBroker/ioBroker.sql/blob/master/README.md", + "icon": "sql.png", + "extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.sql/master/admin/sql.png", + "type": "storage", + "dependencies": [ + { + "js-controller": ">=3.3.0" + } + ], + "globalDependencies": [ + { + "admin": ">=5.1.28" + } + ], + "licenseInformation": { + "license": "MIT", + "type": "free" }, - "link": "https://github.com/ioBroker/ioBroker.sql/blob/master/README.md#settings", - "level": "warn", - "linkText": { - "en": "Readme", - "de": "Liesmich", - "ru": "Прочти меня", - "pt": "Leia-me", - "nl": "Leesmij", - "fr": "Lisez-moi", - "it": "Leggimi", - "es": "Léame", - "pl": "Readme", - "zh-cn": "自述文件" + "supportStopInstance": 10000, + "stopTimeout": 10000, + "plugins": { + "sentry": { + "dsn": "https://c30462d3e0834993b0aa29824346701e@sentry.iobroker.net/45" + }, + "docker": { + "iobDockerComposeFiles": ["docker-compose.yaml"] + } }, - "buttons": [ - "agree", - "cancel" + "adminUI": { + "config": "json", + "custom": "json" + }, + "tier": 1, + "eraseOnUpload": true, + "connectionType": "none", + "dataSource": "push", + "messages": [ + { + "condition": { + "operand": "and", + "rules": ["oldVersion<2.0.0", "newVersion>=2.0.0"] + }, + "title": { + "en": "Important notice!", + "de": "Wichtiger Hinweis!", + "ru": "Важное замечание!", + "pt": "Notícia importante!", + "nl": "Belangrijke mededeling!", + "fr": "Avis important!", + "it": "Avviso IMPORTANTE!", + "es": "Noticia importante!", + "pl": "Ważna uwaga!", + "zh-cn": "重要通知!" + }, + "text": { + "en": "This new version introduces several changes how values are logged because we fixed several issues and added new features. Please especially check the defined \"debounce\" and \"block time\" settings in your datapoints if values are not logged as expected and make sure the settings make sense. For more details please refer to the changelog and Readme.", + "de": "Diese neue Version führt mehrere Änderungen ein, wie Werte protokolliert werden, da wir mehrere Probleme behoben und neue Funktionen hinzugefügt haben. Bitte überprüfen Sie insbesondere die definierten „Entprellen“- und „Sperrzeit“-Einstellungen in Ihren Datenpunkten, wenn Werte nicht wie erwartet protokolliert werden, und stellen Sie sicher, dass die Einstellungen sinnvoll sind. Weitere Details finden Sie im Changelog und in der Readme.", + "ru": "В этой новой версии внесено несколько изменений в регистрацию значений, поскольку мы исправили несколько проблем и добавили новые функции. Пожалуйста, особенно проверьте определенные настройки «debounce» и «block time» в ваших точках данных, если значения не регистрируются должным образом, и убедитесь, что настройки имеют смысл. Для получения более подробной информации, пожалуйста, обратитесь к журналу изменений и Readme.", + "pt": "Esta nova versão apresenta várias alterações na forma como os valores são registrados, pois corrigimos vários problemas e adicionamos novos recursos. Verifique especialmente as configurações definidas de \"debounce\" e \"block time\" em seus pontos de dados se os valores não forem registrados conforme o esperado e verifique se as configurações fazem sentido. Para obter mais detalhes, consulte o log de alterações e o Leiame.", + "nl": "Deze nieuwe versie introduceert verschillende wijzigingen in de manier waarop waarden worden geregistreerd, omdat we verschillende problemen hebben opgelost en nieuwe functies hebben toegevoegd. Controleer vooral de gedefinieerde \"debounce\" en \"block time\" instellingen in uw datapunten als de waarden niet zijn vastgelegd zoals verwacht en zorg ervoor dat de instellingen kloppen. Raadpleeg de changelog en Readme voor meer informatie.", + "fr": "Cette nouvelle version introduit plusieurs changements dans la journalisation des valeurs car nous avons corrigé plusieurs problèmes et ajouté de nouvelles fonctionnalités. Veuillez vérifier en particulier les paramètres \"anti-rebond\" et \"temps de blocage\" définis dans vos points de données si les valeurs ne sont pas enregistrées comme prévu et assurez-vous que les paramètres ont un sens. Pour plus de détails, veuillez consulter le journal des modifications et le fichier Lisez-moi.", + "it": "Questa nuova versione introduce diverse modifiche al modo in cui i valori vengono registrati perché abbiamo risolto diversi problemi e aggiunto nuove funzionalità. Controllare in particolare le impostazioni definite \"debounce\" e \"block time\" nei propri datapoint se i valori non sono registrati come previsto e assicurarsi che le impostazioni abbiano un senso. Per maggiori dettagli, fare riferimento al log delle modifiche e al Leggimi.", + "es": "Esta nueva versión introduce varios cambios en la forma en que se registran los valores porque solucionamos varios problemas y agregamos nuevas funciones. Verifique especialmente la configuración definida de \"antirrebote\" y \"tiempo de bloqueo\" en sus puntos de datos si los valores no se registran como se esperaba y asegúrese de que la configuración tenga sentido. Para obtener más detalles, consulte el registro de cambios y el archivo Léame.", + "pl": "Ta nowa wersja wprowadza kilka zmian w sposobie rejestrowania wartości, ponieważ naprawiliśmy kilka problemów i dodaliśmy nowe funkcje. W szczególności sprawdź zdefiniowane ustawienia „odbicia” i „czasu blokowania” w punktach danych, jeśli wartości nie są rejestrowane zgodnie z oczekiwaniami i upewnij się, że ustawienia mają sens. Aby uzyskać więcej informacji, zapoznaj się z dziennikiem zmian i Readme.", + "zh-cn": "这个新版本引入了一些更改值的记录方式,因为我们修复了几个问题并添加了新功能。如果未按预期记录值,请特别检查数据点中定义的“去抖动”和“阻塞时间”设置,并确保设置有意义。有关更多详细信息,请参阅更改日志和自述文件。" + }, + "link": "https://github.com/ioBroker/ioBroker.sql/blob/master/README.md#settings", + "level": "warn", + "linkText": { + "en": "Readme", + "de": "Liesmich", + "ru": "Прочти меня", + "pt": "Leia-me", + "nl": "Leesmij", + "fr": "Lisez-moi", + "it": "Leggimi", + "es": "Léame", + "pl": "Readme", + "zh-cn": "自述文件" + }, + "buttons": ["agree", "cancel"] + } ] - } - ] - }, - "native": { - "connLink": "", - "debounce": 1000, - "retention": 31536000, - "host": "localhost", - "port": 0, - "user": "", - "password": "", - "dbtype": "sqlite", - "fileName": "sqlite.db", - "requestInterval": 0, - "encrypt": false, - "round": 4, - "dbname": "iobroker", - "multiRequests": true, - "maxConnections": 100, - "changesRelogInterval": 0, - "changesMinDelta": 0, - "writeNulls": true, - "doNotCreateDatabase": false, - "maxLength": 0, - "blockTime": 0, - "debounceTime": 0, - "disableSkippedValueLogging": false, - "enableLogging": false, - "rejectUnauthorized": false, - "customRetentionDuration": 365 - }, - "instanceObjects": [ - { - "_id": "info", - "type": "channel", - "common": { - "name": "Information" - }, - "native": {} }, - { - "_id": "info.connection", - "type": "state", - "common": { - "role": "indicator.connected", - "name": "If connected to DB", - "type": "boolean", - "read": true, - "write": false, - "def": false - }, - "native": {} - } - ], - "objects": [] + "native": { + "connLink": "", + "debounce": 1000, + "retention": 31536000, + "host": "localhost", + "port": 0, + "user": "", + "password": "", + "dbtype": "sqlite", + "fileName": "sqlite.db", + "requestInterval": 0, + "encrypt": false, + "round": 4, + "dbname": "iobroker", + "multiRequests": true, + "maxConnections": 100, + "changesRelogInterval": 0, + "changesMinDelta": 0, + "writeNulls": true, + "doNotCreateDatabase": false, + "maxLength": 0, + "blockTime": 0, + "debounceTime": 0, + "disableSkippedValueLogging": false, + "enableLogging": false, + "rejectUnauthorized": false, + "customRetentionDuration": 365, + "dockerMysql": { + "enabled": false, + "bind": "127.0.0.1", + "stopIfInstanceStopped": true, + "port": "3306", + "autoImageUpdate": true, + "rootPassword": "root_iobroker" + }, + "dockerPhpMyAdmin": { + "enabled": false, + "bind": "0.0.0.0", + "stopIfInstanceStopped": true, + "port": "8080", + "autoImageUpdate": true + } + }, + "instanceObjects": [ + { + "_id": "info", + "type": "channel", + "common": { + "name": "Information" + }, + "native": {} + }, + { + "_id": "info.connection", + "type": "state", + "common": { + "role": "indicator.connected", + "name": "If connected to DB", + "type": "boolean", + "read": true, + "write": false, + "def": false + }, + "native": {} + } + ], + "objects": [] } diff --git a/lib/aggregate.js b/lib/aggregate.js deleted file mode 100644 index f001b336..00000000 --- a/lib/aggregate.js +++ /dev/null @@ -1,1000 +0,0 @@ -/* jshint -W097 */ -/* jshint strict: false */ -/* jslint node: true */ -'use strict'; -// THIS file should be identical with sql and history adapter's one - -function initAggregate(options) { - let log = () => {}; - if (options.debugLog) { - log = options.log || console.log; - } - - // step; // 1 Step is 1 second - if (options.step === null) { - options.step = (options.end - options.start) / options.count; - } - - // Limit 2000 - if ((options.end - options.start) / options.step > options.limit) { - options.step = (options.end - options.start) / options.limit; - } - - options.maxIndex = Math.ceil(((options.end - options.start) / options.step) - 1); - options.result = []; - options.averageCount = []; - options.quantileDatapoints = []; - options.integralDatapoints = []; - options.aggregate = options.aggregate || 'minmax'; - options.overallLength = 0; - - if (options.aggregate === 'percentile') { - if (typeof options.percentile !== 'number' || options.percentile < 0 || options.percentile > 100) { - options.percentile = 50; - } - options.quantile = options.percentile / 100; // Internally we use quantile for percentile too - } - if (options.aggregate === 'quantile') { - if (typeof options.quantile !== 'number' || options.quantile < 0 || options.quantile > 1) { - options.quantile = 0.5; - } - } - if (options.aggregate === 'integral') { - if (typeof options.integralUnit !== 'number' || options.integralUnit <= 0) { - options.integralUnit = 60; - } - options.integralUnit *= 1000; // Convert to milliseconds - } - - log(`${options.logId} Initialize: maxIndex = ${options.maxIndex}, step = ${options.step}, start = ${options.start}, end = ${options.end}`); - // pre-fill the result with timestamps (add one before start and one after end) - try { - options.result.length = options.maxIndex + 2; - } catch (err) { - err.message += `: ${options.maxIndex + 2}`; - throw err; - } - // We define the array length but do not prefill values, we do that on runtime when needed - options.result[0] = { - val: {ts: null, val: null}, - max: {ts: null, val: null}, - min: {ts: null, val: null}, - start: {ts: null, val: null}, - end: {ts: null, val: null} - }; - options.result[options.maxIndex + 2] = { - val: {ts: null, val: null}, - max: {ts: null, val: null}, - min: {ts: null, val: null}, - start: {ts: null, val: null}, - end: {ts: null, val: null} - }; - - if (options.aggregate === 'average') { - options.averageCount[0] = 0; - options.averageCount[options.maxIndex + 2] = 0; - } - - if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { - options.quantileDatapoints[0] = []; - options.quantileDatapoints[options.maxIndex + 2] = []; - } - if (options.aggregate === 'integral') { - options.integralDatapoints[0] = []; - options.integralDatapoints[options.maxIndex + 2] = []; - } - return options; -} - -function aggregation(options, data) { - let index; - let preIndex; - - let collectedTooEarlyData = []; - let collectedTooLateData = []; - let preIndexValueFound = false; - let postIndexValueFound = false; - - for (let i = 0; i < data.length; i++) { - if (!data[i]) { - continue; - } - if (typeof data[i].ts !== 'number') { - data[i].ts = parseInt(data[i].ts, 10); - } - - preIndex = Math.floor((data[i].ts - options.start) / options.step); - - // store all border values - if (preIndex < 0) { - index = 0; - // if the ts is even earlier than the "pre-interval" ignore it, else we collect all data there - if (preIndex < -1) { - collectedTooEarlyData.push(data[i]); - continue; - } - preIndexValueFound = true; - } else if (preIndex > options.maxIndex) { - index = options.maxIndex + 2; - // if the ts is even later than the "post-interval" ignore it, else we collect all data there - if (preIndex > options.maxIndex + 1) { - collectedTooLateData.push(data[i]); - continue; - } - postIndexValueFound = true; - } else { - index = preIndex + 1; - } - options.overallLength++; - - if (options.result[index] === undefined) { // lazy initialization of data structure - options.result[index] = { - val: {ts: null, val: null}, - max: {ts: null, val: null}, - min: {ts: null, val: null}, - start: {ts: null, val: null}, - end: {ts: null, val: null}, - }; - - if (options.aggregate === 'average' || options.aggregate === 'count') { - options.averageCount[index] = 0; - } - - if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { - options.quantileDatapoints[index] = []; - } - if (options.aggregate === 'integral') { - options.integralDatapoints[index] = []; - } - } - - aggregationLogic(data[i], index, options); - } - - // If no data was found in the pre-interval, but we have earlier data, we put the latest of them in the pre-interval - if (!preIndexValueFound && collectedTooEarlyData.length > 0) { - collectedTooEarlyData = collectedTooEarlyData.sort(sortByTs); - options.overallLength++; - aggregationLogic(collectedTooEarlyData[collectedTooEarlyData.length - 1], 0, options); - } - // If no data was found in the post-interval, but we have later data, we put the earliest of them in the post-interval - if (!postIndexValueFound && collectedTooLateData.length > 0) { - collectedTooLateData = collectedTooLateData.sort(sortByTs); - options.overallLength++; - aggregationLogic(collectedTooLateData[0], options.maxIndex + 2, options); - } - - return { result: options.result, step: options.step, sourceLength: data.length }; -} - -function aggregationLogic(data, index, options) { - let log = () => {}; - if (options.debugLog) { - log = options.log || console.log; - } - - if (!options.result[index]) { - log(`${options.logId} Data index ${index} not initialized, ignore!`); - return; - } - - if (options.aggregate !== 'minmax' && !options.result[index].val.ts) { - options.result[index].val.ts = Math.round(options.start + (((index - 1) + 0.5) * options.step)); - } - - if (options.aggregate === 'max') { - if (options.result[index].val.val === null || options.result[index].val.val < data.val) { - options.result[index].val.val = data.val; - } - } else if (options.aggregate === 'min') { - if (options.result[index].val.val === null || options.result[index].val.val > data.val) { - options.result[index].val.val = data.val; - } - } else if (options.aggregate === 'average') { - options.result[index].val.val += parseFloat(data.val); - options.averageCount[index]++; - } else if (options.aggregate === 'count') { - options.averageCount[index]++; - } else if (options.aggregate === 'total') { - options.result[index].val.val += parseFloat(data.val); - } else if (options.aggregate === 'minmax') { - if (options.result[index].min.ts === null) { - options.result[index].min.ts = data.ts; - options.result[index].min.val = data.val; - - options.result[index].max.ts = data.ts; - options.result[index].max.val = data.val; - - options.result[index].start.ts = data.ts; - options.result[index].start.val = data.val; - - options.result[index].end.ts = data.ts; - options.result[index].end.val = data.val; - } else { - if (data.val !== null) { - if (data.val > options.result[index].max.val) { - options.result[index].max.ts = data.ts; - options.result[index].max.val = data.val; - } else if (data.val < options.result[index].min.val) { - options.result[index].min.ts = data.ts; - options.result[index].min.val = data.val; - } - if (data.ts > options.result[index].end.ts) { - options.result[index].end.ts = data.ts; - options.result[index].end.val = data.val; - } - } else { - if (data.ts > options.result[index].end.ts) { - options.result[index].end.ts = data.ts; - options.result[index].end.val = null; - } - } - } - } else if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { - options.quantileDatapoints[index].push(data.val); - log(`${options.logId} Quantile ${index}: Add ts= ${data.ts} val=${data.val}`); - } else if (options.aggregate === 'integral') { - options.integralDatapoints[index].push(data); - log(`${options.logId} Integral ${index}: Add ts= ${data.ts} val=${data.val}`); - } -} - -function finishAggregation(options) { - let log = () => {}; - if (options.debugLog) { - log = options.log || console.log; - } - - if (options.aggregate === 'minmax') { - let preBorderValueRemoved = false; - let postBorderValueRemoved = false; - const originalResultLength = options.result.length; - - let startIndex = 0; - let endIndex = options.result.length; - const finalResult = []; - - for (let ii = startIndex; ii < endIndex; ii++) { - // no one value in this period - if (options.result[ii] === undefined || options.result[ii].start.ts === null) { - if (ii === 0) { - preBorderValueRemoved = true; - } else if (ii === originalResultLength - 1) { - postBorderValueRemoved = true; - } - // options.result.splice(ii, 1); - continue; - } - // just one value in this period: max == min == start == end - if (options.result[ii].start.ts === options.result[ii].end.ts) { - finalResult.push({ - ts: options.result[ii].start.ts, - val: options.result[ii].start.val - }); - } else - if (options.result[ii].min.ts === options.result[ii].max.ts) { - // if just 2 values: start == min == max, end - if (options.result[ii].start.ts === options.result[ii].min.ts || - options.result[ii].end.ts === options.result[ii].min.ts) { - finalResult.push({ - ts: options.result[ii].start.ts, - val: options.result[ii].start.val - }); - finalResult.push({ - ts: options.result[ii].end.ts, - val: options.result[ii].end.val - }); - } // if just 3 values: start, min == max, end - else { - finalResult.push({ - ts: options.result[ii].start.ts, - val: options.result[ii].start.val - }); - finalResult.push({ - ts: options.result[ii].max.ts, - val: options.result[ii].max.val - }); - finalResult.push({ - ts: options.result[ii].end.ts, - val: options.result[ii].end.val - }); - } - } else - if (options.result[ii].start.ts === options.result[ii].max.ts) { - // just one value in this period: start == max, min == end - if (options.result[ii].min.ts === options.result[ii].end.ts) { - finalResult.push({ - ts: options.result[ii].start.ts, - val: options.result[ii].start.val - }); - finalResult.push({ - ts: options.result[ii].end.ts, - val: options.result[ii].end.val - }); - } // start == max, min, end - else { - finalResult.push({ - ts: options.result[ii].start.ts, - val: options.result[ii].start.val - }); - finalResult.push({ - ts: options.result[ii].min.ts, - val: options.result[ii].min.val - }); - finalResult.push({ - ts: options.result[ii].end.ts, - val: options.result[ii].end.val - }); - } - } else - if (options.result[ii].end.ts === options.result[ii].max.ts) { - // just one value in this period: start == min, max == end - if (options.result[ii].min.ts === options.result[ii].start.ts) { - finalResult.push({ - ts: options.result[ii].start.ts, - val: options.result[ii].start.val - }); - finalResult.push({ - ts: options.result[ii].end.ts, - val: options.result[ii].end.val - }); - } // start, min, max == end - else { - finalResult.push({ - ts: options.result[ii].start.ts, - val: options.result[ii].start.val - }); - finalResult.push({ - ts: options.result[ii].min.ts, - val: options.result[ii].min.val - }); - finalResult.push({ - ts: options.result[ii].end.ts, - val: options.result[ii].end.val - }); - } - } else - if (options.result[ii].start.ts === options.result[ii].min.ts || - options.result[ii].end.ts === options.result[ii].min.ts) { - // just one value in this period: start == min, max, end - finalResult.push({ - ts: options.result[ii].start.ts, - val: options.result[ii].start.val - }); - finalResult.push({ - ts: options.result[ii].max.ts, - val: options.result[ii].max.val - }); - finalResult.push({ - ts: options.result[ii].end.ts, - val: options.result[ii].end.val - }); - } else { - finalResult.push({ - ts: options.result[ii].start.ts, - val: options.result[ii].start.val - }); - // just one value in this period: start == min, max, end - if (options.result[ii].max.ts > options.result[ii].min.ts) { - finalResult.push({ - ts: options.result[ii].min.ts, - val: options.result[ii].min.val - }); - finalResult.push({ - ts: options.result[ii].max.ts, - val: options.result[ii].max.val - }); - } else { - finalResult.push({ - ts: options.result[ii].max.ts, - val: options.result[ii].max.val - }); - finalResult.push({ - ts: options.result[ii].min.ts, - val: options.result[ii].min.val - }); - } - finalResult.push({ - ts: options.result[ii].end.ts, - val: options.result[ii].end.val - }); - } - } - if (options.removeBorderValues) { // we cut out the additional results - if (!preBorderValueRemoved) { - finalResult.splice(0, 1); - } - if (!postBorderValueRemoved) { - finalResult.length--; - } - } - options.result = finalResult; - } else if (options.aggregate === 'average') { - let startIndex = 0; - let endIndex = options.result.length; - const finalResult = []; - if (options.removeBorderValues) { // we cut out the additional results - // options.result.splice(0, 1); - // options.averageCount.splice(0, 1); - // options.result.length--; - // options.averageCount.length--; - startIndex++; - endIndex--; - } - for (let k = startIndex; k < endIndex; k++) { - if (options.result[k] !== undefined && options.result[k].val.ts) { - finalResult.push({ - ts: options.result[k].val.ts, - val: options.result[k].val.val !== null ? Math.round(options.result[k].val.val / options.averageCount[k] * 100) / 100 : null - }); - } else { - // no one value in this interval - // options.result.splice(k, 1); - // options.averageCount.splice(k, 1); // not needed to clean up because not used anymore afterwards - } - } - options.result = finalResult; - } else if (options.aggregate === 'count') { - let startIndex = 0; - let endIndex = options.result.length; - const finalResult = []; - if (options.removeBorderValues) { // we cut out the additional results - // options.result.splice(0, 1); - // options.averageCount.splice(0, 1); - // options.result.length--; - // options.averageCount.length--; - startIndex++; - endIndex--; - } - for (let k = startIndex; k < endIndex; k++) { - if (options.result[k] !== undefined && options.result[k].val.ts) { - finalResult.push({ - ts: options.result[k].val.ts, - val: options.averageCount[k], - }); - } else { - // no one value in this interval - // options.result.splice(k, 1); - // options.averageCount.splice(k, 1); // not needed to clean up because not used anymore afterward - } - } - options.result = finalResult; - } else if (options.aggregate === 'integral') { - let preBorderValueRemoved = false; - let postBorderValueRemoved = false; - const originalResultLength = options.result.length; - const finalResult = []; - - for (let k = 0; k < options.result.length; k++) { - let indexStartTs = options.start + ((k - 1) * options.step); - let indexEndTs = indexStartTs + options.step; - if (options.integralDatapoints[k] && options.integralDatapoints[k].length) { - // Sort data points by ts first - options.integralDatapoints[k].sort(sortByTs); - } - // Make sure that we have entries that always start at the beginning of the interval - if ((!options.integralDatapoints[k] || !options.integralDatapoints[k].length || options.integralDatapoints[k][0].ts > indexStartTs) && options.integralDatapoints[k - 1] && options.integralDatapoints[k -1][options.integralDatapoints[k - 1].length - 1]) { - // if the first entry of this interval started somewhere in the start of the interval, add a start entry - // same if there is no entry at all in the timeframe, use last entry from interval before - options.integralDatapoints[k] = options.integralDatapoints[k] || []; - options.integralDatapoints[k].unshift({ - ts: indexStartTs, - val: options.integralDatapoints[k - 1][options.integralDatapoints[k - 1].length - 1].val - }); - log(`${options.logId} Integral: ${k}: Added start entry for interval with ts=${indexStartTs}, val=${options.integralDatapoints[k][0].val}`); - } else if (options.integralDatapoints[k] && options.integralDatapoints[k].length && options.integralDatapoints[k][0].ts > indexStartTs) { - options.integralDatapoints[k].unshift({ - ts: indexStartTs, - val: options.integralDatapoints[k][0].val - }); - log(`${options.logId} Integral: ${k}: Added start entry for interval with ts=${indexStartTs}, val=${options.integralDatapoints[k][0].val} with same value as first point in interval because no former datapoint was found`); - } else if (options.integralDatapoints[k] && options.integralDatapoints[k].length && options.integralDatapoints[k][0].ts < indexStartTs) { - // if the first entry of this interval started before the start of the interval, search for the last value before the start of the interval, add as start entry - let preFirstIndex = null; - for (let kk = 0; kk < options.integralDatapoints[k].length; kk++) { - if (options.integralDatapoints[k][kk].ts >= indexStartTs) { - break; - } - preFirstIndex = kk; - } - if (preFirstIndex !== null) { - options.integralDatapoints[k].splice(0, preFirstIndex, { - ts: indexStartTs, - val: options.integralDatapoints[k][preFirstIndex].val - }); - log(`${options.logId} Integral: ${k}: Remove ${preFirstIndex + 1} entries and add start entry for interval with ts=${indexStartTs}, val=${options.integralDatapoints[k][0].val}`); - } - } - - const point = { - ts: options.result[k] !== undefined && options.result[k].val.ts ? options.result[k].val.ts : Math.round(options.start + (((k - 1) + 0.5) * options.step)), - val: null - } - - const integralDatapoints = options.integralDatapoints[k] || []; - const vals = integralDatapoints.map(dp => `[${dp.ts}, ${dp.val}]`); - log(`${options.logId} Integral: ${k}: ${integralDatapoints.length} datapoints for interval for ${indexStartTs} - ${indexEndTs}: ${vals.join(',')}`); - - // Calculate Intervals and always calculate till the interval end (start made sure above already) - for (let kk = 0; kk < integralDatapoints.length; kk++) { - const valEndTs = integralDatapoints[kk + 1] ? Math.min(integralDatapoints[kk + 1].ts, indexEndTs) : indexEndTs; - let valDuration = valEndTs - integralDatapoints[kk].ts; - if (valDuration < 0) { - log(`${options.logId} Integral: ${k}[${kk}] data do not belong to this interval, ignore ${JSON.stringify(integralDatapoints[kk])} (vs. ${valEndTs})`) - break; - } - if (valDuration === 0) { - log(`${options.logId} Integral: ${k}[${kk}] valDuration zero, ignore ${JSON.stringify(integralDatapoints[kk])}`) - continue; - } - let valStart = parseFloat(integralDatapoints[kk].val) || 0; - // End value is the next value, or if none, assume "linearity - let valEnd = parseFloat((integralDatapoints[kk + 1] ? integralDatapoints[kk + 1].val : (options.integralDatapoints[k + 1] && options.integralDatapoints[k + 1][0] ? options.integralDatapoints[k + 1][0].val : valStart))) || 0; - if (options.integralInterpolation !== 'linear' || valStart === valEnd) { - const integralAdd = valStart * valDuration / options.integralUnit; - // simple rectangle linear interpolation - log(`${options.logId} Integral: ${k}[${kk}] : Add ${integralAdd} from val=${valStart} for ${valDuration}`); - point.val += integralAdd; - } else if ((valStart >= 0 && valEnd >= 0) || (valStart <= 0 && valEnd <= 0)) { - // start and end are both positive or both negative, or one is 0 - let multiplier = 1; - if (valStart <= 0 && valEnd <= 0) { - multiplier = -1; // correct the sign at the end - valStart = -valStart; - valEnd = -valEnd; - } - const minVal = Math.min(valStart, valEnd); - const maxVal = Math.max(valStart, valEnd); - const rectPart = minVal * valDuration / options.integralUnit; - const trianglePart = (maxVal - minVal) * valDuration * 0.5 / options.integralUnit; - const integralAdd = (rectPart + trianglePart) * multiplier; - log(`${options.logId} Integral: ${k}[${kk}] : Add R${rectPart} + T${trianglePart} => ${integralAdd} from val=${valStart} to ${valEnd} for ${valDuration}`); - point.val += integralAdd; - } else { - // Values are on different sides of 0, so we need to find the 0 crossing - const zeroCrossing = Math.abs((valStart * valDuration) / (valEnd - valStart)); - // Then calculate two linear segments, one from 0 to the crossing, and one from the crossing to the end - const trianglePart1 = valStart * zeroCrossing * 0.5 / options.integralUnit; - const trianglePart2 = valEnd * (valDuration - zeroCrossing) * 0.5 / options.integralUnit; - const integralAdd = trianglePart1 + trianglePart2; - log(`${options.logId} Integral: ${k}[${kk}] : Add T${trianglePart1} + T${trianglePart2} => ${integralAdd} from val=${valStart} to ${valEnd} for ${valDuration} (zero crossing ${zeroCrossing})`); - point.val += integralAdd; - } - } - /*options.result[k] = { - ts: options.result[k].val.ts, - val: options.result[k].val.val - }*/ - if (point.val !== null) { - finalResult.push(point); - } else { - if (k === 0) { - preBorderValueRemoved = true; - } else if (k === originalResultLength - 1) { - postBorderValueRemoved = true; - } - } - } - if (options.removeBorderValues) { // we cut out the additional results - if (!preBorderValueRemoved) { - finalResult.splice(0, 1); - } - if (!postBorderValueRemoved) { - finalResult.length--; - } - } - options.result = finalResult; - } else if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { - let startIndex = 0; - let endIndex = options.result.length; - const finalResult = []; - if (options.removeBorderValues) { // we cut out the additional results - /* - options.result.splice(0, 1); - options.quantileDatapoints.splice(0, 1); - options.result.length-- - options.quantileDatapoints.length--; - */ - startIndex++; - endIndex--; - } - for (let k = startIndex; k < endIndex; k++) { - if (options.result[k] !== undefined && options.result[k].val.ts) { - const point = { - ts: options.result[k].val.ts, - val: quantile(options.quantile, options.quantileDatapoints[k]) - }; - log(`${options.logId} Quantile ${k} ${point.ts}: ${options.quantileDatapoints[k].join(', ')} -> ${point.val}`); - finalResult.push(point); - } else { - // no one value in this interval - // options.result.splice(k, 1); - // options.quantileDatapoints.splice(k, 1); // not needed to clean up because not used anymore afterward - } - } - options.result = finalResult; - } else { - let startIndex = 0; - let endIndex = options.result.length; - const finalResult = []; - if (options.removeBorderValues) { // we cut out the additional results - // options.result.splice(0, 1); - // options.result.length--; - startIndex++; - endIndex--; - } - for (let j = startIndex; j < endIndex; j++) { - if (options.result[j] !== undefined && options.result[j].val.ts) { - finalResult.push({ - ts: options.result[j].val.ts, - val: options.result[j].val.val - }); - } else { - // no one value in this interval - //options.result.splice(j, 1); - } - } - options.result = finalResult; - } - - beautify(options); -} - -function beautify(options) { - let log = () => {}; - if (options.debugLog) { - log = options.log || console.log; - } - - log(`${options.logId} Beautify: ${options.result.length} results`); - let preFirstValue = null; - let postLastValue = null; - - if (options.ignoreNull === 'true') { // include nulls and replace them with last value - options.ignoreNull = true; - } else - if (options.ignoreNull === 'false') { // include nulls - options.ignoreNull = false; - } else - if (options.ignoreNull === '0') { // include nulls and replace them with 0 - options.ignoreNull = 0; - } else - if (options.ignoreNull !== true && options.ignoreNull !== false && options.ignoreNull !== 0) { - options.ignoreNull = false; - } - - // process null values, remove points outside the span and find first points after end and before start - for (let i = 0; i < options.result.length; i++) { - if (options.ignoreNull !== false) { - // if value is null - if (options.result[i].val === null) { - // null value must be replaced with last not null value - if (options.ignoreNull === true) { - // remove value - options.result.splice(i, 1); - i--; - continue; - } else { - // null value must be replaced with 0 - options.result[i].val = options.ignoreNull; - } - } - } - - // remove all not requested points - if (options.result[i].ts < options.start) { - preFirstValue = options.result[i].val !== null ? options.result[i] : null; - options.result.splice(i, 1); - i--; - continue; - } - - postLastValue = options.result[i].val !== null ? options.result[i] : null; - - if (options.result[i].ts > options.end) { - options.result.splice(i, options.result.length - i); - break; - } - } - - // check start and stop - if (options.result.length && options.aggregate !== 'none' && !options.removeBorderValues) { - const firstTS = options.result[0].ts; - - if (firstTS > options.start && !options.removeBorderValues) { - if (preFirstValue) { - const firstY = options.result[0].val; - // if steps - if (options.aggregate === 'onchange' || !options.aggregate) { - if (preFirstValue.ts !== firstTS) { - options.result.unshift({ts: options.start, val: preFirstValue.val}); - } else { - if (options.ignoreNull) { - options.result.unshift({ts: options.start, val: firstY}); - } - } - } else { - if (preFirstValue.ts !== firstTS) { - if (firstY !== null) { - // interpolate - const y = preFirstValue.val + (firstY - preFirstValue.val) * (options.start - preFirstValue.ts) / (firstTS - preFirstValue.ts); - options.result.unshift({ts: options.start, val: y, i: true}); - log(`${options.logId} interpolate ${y} from ${preFirstValue.val} to ${firstY} as first return value`); - } else { - options.result.unshift({ts: options.start, val: null}); - } - } else { - if (options.ignoreNull) { - options.result.unshift({ts: options.start, val: firstY}); - } - } - } - } else { - if (options.ignoreNull) { - options.result.unshift({ts: options.start, val: options.result[0].val}); - } else { - options.result.unshift({ts: options.start, val: null}); - } - } - } - - const lastTS = options.result[options.result.length - 1].ts; - if (lastTS < options.end && !options.removeBorderValues) { - if (postLastValue) { - // if steps - if (options.aggregate === 'onchange' || !options.aggregate) { - // if more data following, draw line to the end of the chart - if (postLastValue.ts !== lastTS) { - options.result.push({ts: options.end, val: postLastValue.val}); - } else { - if (options.ignoreNull) { - options.result.push({ts: options.end, val: postLastValue.val}); - } - } - } else { - if (postLastValue.ts !== lastTS) { - const lastY = options.result[options.result.length - 1].val; - if (lastY !== null) { - // make interpolation - const _y = lastY + (postLastValue.val - lastY) * (options.end - lastTS) / (postLastValue.ts - lastTS); - options.result.push({ts: options.end, val: _y, i: true}); - log(`${options.logId} interpolate ${_y} from ${lastY} to ${postLastValue.val} as last return value`); - } else { - options.result.push({ts: options.end, val: null}); - } - } else { - if (options.ignoreNull) { - options.result.push({ts: options.end, val: postLastValue.val}); - } - } - } - } else { - if (options.ignoreNull) { - const lastY = options.result[options.result.length - 1].val; - // if no more data, that means do not draw line - options.result.push({ts: options.end, val: lastY}); - } else { - // if no more data, that means do not draw line - options.result.push({ts: options.end, val: null}); - } - } - } - } else if (options.aggregate === 'none') { - if (options.count && options.result.length > options.count) { - options.result.splice(0, options.result.length - options.count); - } - } - - if (options.addId) { - for (let i = 0; i < options.result.length; i++) { - if (!options.result[i].id && options.id) { - options.result[i].id = options.index || options.id; - } - } - } -} - -function sendResponse(adapter, msg, options, data, startTime) { - let aggregateData; - if (typeof data === 'string') { - adapter.log.error(data); - return adapter.sendTo(msg.from, msg.command, { - result: [], - step: 0, - error: data, - sessionId: options.sessionId - }, msg.callback); - } - - if (options.count && !options.start && data.length > options.count) { - data.splice(0, data.length - options.count); - } - if (data[0]) { - options.start = options.start || data[0].ts; - - if (!options.aggregate || options.aggregate === 'onchange' || options.aggregate === 'none' || options.preAggregated) { - aggregateData = {result: data, step: 0, sourceLength: data.length}; - - // convert ack from 0/1 to false/true - if (options.ack && aggregateData.result) { - for (let i = 0; i < aggregateData.result.length; i++) { - aggregateData.result[i].ack = !!aggregateData.result[i].ack; - } - } - options.result = aggregateData.result; - - beautify(options); - - if (options.aggregate === 'none' && options.count && options.result.length > options.count) { - options.result.splice(0, options.result.length - options.count); - } - aggregateData.result = options.result; - } else { - initAggregate(options); - aggregateData = aggregation(options, data); - finishAggregation(options); - aggregateData.result = options.result; - } - - adapter.log.debug(`Send: ${aggregateData.result.length} of: ${aggregateData.sourceLength} in: ${Date.now() - startTime}ms`); - - adapter.sendTo(msg.from, msg.command, { - result: aggregateData.result, - step: aggregateData.step, - error: null, - sessionId: options.sessionId - }, msg.callback); - } else { - adapter.log.info('No Data'); - adapter.sendTo(msg.from, msg.command, {result: [], step: null, error: null, sessionId: options.sessionId}, msg.callback); - } -} - -function sendResponseCounter(adapter, msg, options, data, startTime) { - // data - // 1586713810000 100 - // 1586713810010 200 - // 1586713810040 500 - // 1586713810050 0 - // 1586713810090 400 - // 1586713810100 0 - // 1586713810110 100 - if (typeof data === 'string') { - adapter.log.error(data); - return adapter.sendTo(msg.from, msg.command, { - result: [], - error: data, - sessionId: options.sessionId - }, msg.callback); - } - - if (data[0] && data[1]) { - // first | start | afterFirst | ...... | last | end | afterLast - // 5 | | 8 | 9 | 1 | 3 | | 5 - // | 5+(8-5/tsDiff) | | 9 | 1 | | 3+(5-3/tsDiff) | - // (9 - 6.5) + (4 - 1) - - if (data[1].ts === options.start) { - data.splice(0, 1); - } - - if (data[0].ts < options.start && data[0].val > data[1].val) { - data.splice(0, 1); - } - - // interpolate from first to start time - if (data[0].ts < options.start) { - const val = data[0].val + (data[1].val - data[0].val) * ((options.start - data[0].ts) / (data[1].ts - data[0].ts)); - data.splice(0, 1); - data.unshift({ts: options.start, val, i: true}); - } - - if (data[data.length - 2] !== undefined && data[data.length - 2].ts === options.end) { - data.length--; - } - - const veryLast = data[data.length - 1]; - const beforeLast = data[data.length - 2]; - - // interpolate from end time to last - if (veryLast !== undefined && beforeLast !== undefined && options.end < veryLast.ts) { - const val = beforeLast.val + (veryLast.val - beforeLast.val) * ((options.end - beforeLast.ts) / (veryLast.ts - beforeLast.ts)); - data.length--; - data.push({ts: options.end, val, i: true}); - } - - // at this point we expect [6.5, 9, 1, 4] - // at this point we expect [150, 200, 500, 0, 400, 0, 50] - let sum = 0; - if (data.length > 1) { - let val = data[data.length - 1].val; - for (let i = data.length - 2; i >= 0; i--) { - if (data[i].val < val) { - sum += val - data[i].val; - } - val = data[i].val; - } - } - - adapter.sendTo(msg.from, msg.command, { - result: sum, - error: null, - sessionId: options.sessionId - }, msg.callback); - } else { - adapter.log.info('No Data'); - adapter.sendTo(msg.from, msg.command, {result: 0, step: null, error: null, sessionId: options.sessionId}, msg.callback); - } -} - -/** - * Get quantile value from an array. - * - * @param {Number} q - quantile - * @param {Array|TypedArray} list - list of sorted values (ascending) - * - * @return {number} Quantile value - */ -function getQuantileValue(q, list) { - if (q === 0) return list[0]; - - const index = list.length * q; - if (Number.isInteger(index)) { - // mean of two middle numbers - return (list[index - 1] + list[index]) / 2; - } - return list[Math.ceil(index - 1)]; -} - -/** - * Calculate quantile for given array of values. - * - * @template T - * @param {Number|Array} qOrPs - quantile or a list of quantiles - * @param {Array|Array|TypedArray} list - array of values - * @param {function(T): Number} [fn] - optional function to extract a value from an array item - * - * @return {Number|T|Array|Array} - */ -function quantile(qOrPs, list, fn) { - const q = Array.isArray(qOrPs) ? qOrPs : [qOrPs]; - - list = list.slice().sort(function (a, b) { - if (fn) { - a = fn(a); - b = fn(b); - } - - a = Number.isNaN(a) ? Number.NEGATIVE_INFINITY : a; - b = Number.isNaN(b) ? Number.NEGATIVE_INFINITY : b; - - if (a > b) { - return 1; - } - if (a < b) { - return -1; - } - - return 0; - }); - - if (q.length === 1) { - return getQuantileValue(q[0], list); - } - - return q.map(function (q) { - return getQuantileValue(q, list); - }); -} - -function sortByTs(a, b) { - return a.ts - b.ts; -} - -module.exports.sendResponseCounter = sendResponseCounter; -module.exports.sendResponse = sendResponse; -module.exports.initAggregate = initAggregate; -module.exports.aggregation = aggregation; -module.exports.beautify = beautify; -module.exports.finishAggregation = finishAggregation; -module.exports.sortByTs = sortByTs; diff --git a/lib/mssql-client.js b/lib/mssql-client.js deleted file mode 100644 index 1e1171f3..00000000 --- a/lib/mssql-client.js +++ /dev/null @@ -1,61 +0,0 @@ -// Generated by CoffeeScript 2.3.2 -(function() { - const ConnectionFactory = require('sql-client/lib/connection-factory').ConnectionFactory; - const SQLClient = require('sql-client/lib/sql-client').SQLClient; - const SQLClientPool = require('sql-client/lib/sql-client-pool').SQLClientPool; - const mssql = require('mssql'); - - const MSSQLConnectionFactory = class MSSQLConnectionFactory extends ConnectionFactory { - constructor() { - super(...arguments); - this.open_connection = this.open_connection.bind(this); - this.execute = this.execute.bind(this); - } - - open_connection(options, callback) { - let connection; - const pos = options.server.indexOf(':'); - if (pos !== -1) { - options.port = parseInt(options.server.substring(pos + 1), 10); - options.server = options.server.substring(0, pos); - } - options.pool = options.pool || {}; - options.pool.min = 0; - options.pool.max = 1; - - return connection = new mssql.ConnectionPool(options, err => { - if (err != null) { - return callback(err); - } else { - return callback(null, connection); - } - }); - }; - - execute(connection, sql, bindvars, callback) { - const request = new mssql.Request(connection); - return request.query(sql, (err, result) => callback(err, result && result.recordset ? result.recordset : result)); - }; - }; - - const MSSQLClient = class MSSQLClient extends SQLClient { - constructor(...options) { - super(...options, new MSSQLConnectionFactory()); - } - - }; - - const MSSQLClientPool = class MSSQLClientPool extends SQLClientPool { - constructor(...options) { - super(...options, new MSSQLConnectionFactory()); - } - - }; - - exports.MSSQLConnectionFactory = MSSQLConnectionFactory; - - exports.MSSQLClient = MSSQLClient; - - exports.MSSQLClientPool = MSSQLClientPool; - -}).call(this); diff --git a/lib/mssql.js b/lib/mssql.js deleted file mode 100644 index 041dcaa8..00000000 --- a/lib/mssql.js +++ /dev/null @@ -1,278 +0,0 @@ -exports.init = function (dbname, doNotCreateDatabase) { - const commands = [ - `CREATE TABLE ${dbname}.dbo.sources (id INTEGER NOT NULL PRIMARY KEY IDENTITY(1,1), name varchar(255));`, - `CREATE TABLE ${dbname}.dbo.datapoints (id INTEGER NOT NULL PRIMARY KEY IDENTITY(1,1), name varchar(255), type INTEGER);`, - `CREATE TABLE ${dbname}.dbo.ts_number (id INTEGER, ts BIGINT, val REAL, ack BIT, _from INTEGER, q INTEGER);`, - `CREATE INDEX i_id on ${dbname}.dbo.ts_number (id, ts);`, - `CREATE TABLE ${dbname}.dbo.ts_string (id INTEGER, ts BIGINT, val TEXT, ack BIT, _from INTEGER, q INTEGER);`, - `CREATE INDEX i_id on ${dbname}.dbo.ts_string (id, ts);`, - `CREATE TABLE ${dbname}.dbo.ts_bool (id INTEGER, ts BIGINT, val BIT, ack BIT, _from INTEGER, q INTEGER);`, - `CREATE INDEX i_id on ${dbname}.dbo.ts_bool (id, ts);`, - `CREATE TABLE ${dbname}.dbo.ts_counter (id INTEGER, ts BIGINT, val REAL);`, - `CREATE INDEX i_id on ${dbname}.dbo.ts_counter (id, ts);`, - ]; - !doNotCreateDatabase && commands.unshift(`CREATE DATABASE ${dbname};`); - - return commands; -}; - -exports.destroy = function (dbname) { - return [ - `DROP TABLE ${dbname}.dbo.ts_counter;`, - `DROP TABLE ${dbname}.dbo.ts_number;`, - `DROP TABLE ${dbname}.dbo.ts_string;`, - `DROP TABLE ${dbname}.dbo.ts_bool;`, - `DROP TABLE ${dbname}.dbo.sources;`, - `DROP TABLE ${dbname}.dbo.datapoints;`, - `DROP DATABASE ${dbname};`, - `DBCC FREEPROCCACHE;` - ]; -}; - -exports.getFirstTs = function (dbname, db) { - return `SELECT id, MIN(ts) AS ts FROM ${dbname}.dbo.${db} GROUP BY id;`; -}; - -exports.insert = function (dbname, index, values) { - const insertValues = {} - values.forEach(value => { - // state, from, db - insertValues[value.db] = insertValues[value.db] || []; - - if (!value.state || value.state.val === null || value.state.val === undefined) { - value.state.val = 'NULL'; - } else if (value.db === 'ts_string') { - value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; - } else if (value.db === 'ts_bool') { - value.state.val = value.state.val ? 1 : 0; - } else if (value.db === 'ts_number') { - if (isNaN(value.state.val)) { - value.state.val = 'NULL'; - } - } - - if (value.db === 'ts_counter') { - insertValues[value.db].push(`(${index}, ${value.state.ts}, ${value.state.val})`); - } else { - insertValues[value.db].push(`(${index}, ${value.state.ts}, ${value.state.val}, ${(value.state.ack ? 1 : 0)}, ${value.from || 0}, ${value.state.q || 0})`); - } - }); - - let query = ''; - for (let db in insertValues) { - if (db === 'ts_counter') { - while (insertValues[db].length) { - query += `INSERT INTO ${dbname}.dbo.ts_counter (id, ts, val) VALUES ${insertValues[db].splice(0, 500).join(',')};`; - } - } else { - while (insertValues[db].length) { - query += `INSERT INTO ${dbname}.dbo.${db} (id, ts, val, ack, _from, q) VALUES ${insertValues[db].splice(0, 500).join(',')};`; - } - } - } - - return query; -} - -exports.retention = function (dbname, index, db, retention) { - const d = new Date(); - d.setSeconds(-retention); - let query = `DELETE FROM ${dbname}.dbo.${db} WHERE`; - query += ` id=${index}`; - query += ` AND ts < ${d.getTime()}`; - query += ';'; - return query; -}; - -exports.getIdSelect = function (dbname, name) { - if (!name) { - return `SELECT id, type, name FROM ${dbname}.dbo.datapoints;`; - } else { - return `SELECT id, type, name FROM ${dbname}.dbo.datapoints WHERE name='${name}';`; - } -}; - -exports.getIdInsert = function (dbname, name, type) { - return `INSERT INTO ${dbname}.dbo.datapoints (name, type) VALUES('${name}', ${type});`; -}; - -exports.getIdUpdate = function (dbname, id, type) { - return `UPDATE ${dbname}.dbo.datapoints SET type=${type} WHERE id=${id};`; -}; - -exports.getFromSelect = function (dbname, from) { - if (from) { - return `SELECT id FROM ${dbname}.dbo.sources WHERE name='${from}';`; - } else { - return `SELECT id, name FROM ${dbname}.dbo.sources;`; - - } -}; - -exports.getFromInsert = function (dbname, from) { - return `INSERT INTO ${dbname}.dbo.sources (name) VALUES('${from}');`; -}; - -exports.getCounterDiff = function (dbname, options) { - // Take first real value after start - const subQueryStart = `SELECT TOP 1 val, ts FROM ${dbname}.dbo.ts_number WHERE ${dbname}.dbo.ts_number.id=${options.id} AND ${dbname}.dbo.ts_number.ts>=${options.start} AND ${dbname}.dbo.ts_number.ts<${options.end} AND ${dbname}.dbo.ts_number.val IS NOT NULL ORDER BY ${dbname}.dbo.ts_number.ts ASC`; - // Take last real value before end - const subQueryEnd = `SELECT TOP 1 val, ts FROM ${dbname}.dbo.ts_number WHERE ${dbname}.dbo.ts_number.id=${options.id} AND ${dbname}.dbo.ts_number.ts>=${options.start} AND ${dbname}.dbo.ts_number.ts<${options.end} AND ${dbname}.dbo.ts_number.val IS NOT NULL ORDER BY ${dbname}.dbo.ts_number.ts DESC`; - // Take last value before start - const subQueryFirst = `SELECT TOP 1 val, ts FROM ${dbname}.dbo.ts_number WHERE ${dbname}.dbo.ts_number.id=${options.id} AND ${dbname}.dbo.ts_number.ts< ${options.start} ORDER BY ${dbname}.dbo.ts_number.ts DESC`; - // Take next value after end - const subQueryLast = `SELECT TOP 1 val, ts FROM ${dbname}.dbo.ts_number WHERE ${dbname}.dbo.ts_number.id=${options.id} AND ${dbname}.dbo.ts_number.ts>=${options.end} ORDER BY ${dbname}.dbo.ts_number.ts ASC`; - // get values from counters where counter values changed - const subQueryCounterChanges = `SELECT val, ts FROM ${dbname}.dbo.ts_counter WHERE ${dbname}.dbo.ts_number.id=${options.id} AND ${dbname}.dbo.ts_number.ts>${options.start} AND ${dbname}.dbo.ts_number.ts<${options.end} AND ${dbname}.dbo.ts_number.val IS NOT NULL ORDER BY ${dbname}.dbo.ts_number.ts ASC`; - - return `${subQueryFirst} ` + - `UNION ALL (${subQueryStart}) a ` + - `UNION ALL (${subQueryEnd}) b ` + - `UNION ALL (${subQueryLast}) c` + - `UNION ALL (${subQueryCounterChanges}) d`; -}; - -exports.getHistory = function (dbname, db, options) { - let query = `SELECT * FROM (SELECT `; - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { - query += ` TOP ${options.count}`; - } - query += ` ts, val` + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? `, ack` : '') + - (options.from ? `, ${dbname}.dbo.sources.name as 'from'` : '') + - (options.q ? `, q` : '') + ` FROM ${dbname}.dbo.${db}`; - - if (options.from) { - query += ` INNER JOIN ${dbname}.dbo.sources ON ${dbname}.dbo.sources.id=${dbname}.dbo.${db}._from`; - } - - let where = ''; - - if (options.id) { - where += ` ${dbname}.dbo.${db}.id=${options.id}`; - } - if (options.end) { - where += `${where ? ` AND` : ''} ${dbname}.dbo.${db}.ts < ${options.end}`; - } - if (options.start) { - where += `${where ? ` AND` : ''} ${dbname}.dbo.${db}.ts >= ${options.start}`; - } - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { - where += ` ORDER BY ts`; - - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count && options.returnNewestEntries) ) { - where += ` DESC`; - } else { - where += ` ASC`; - } - } - where += `) AS t`; - if (options.start) { - // add last value before start - let subQuery; - let subWhere; - subQuery = ` SELECT TOP 1 ts, val` + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? `, ack` : '') + - (options.from ? `, ${dbname}.dbo.sources.name as 'from'` : '') + - (options.q ? `, q` : '') + ` FROM ${dbname}.dbo.${db}`; - if (options.from) { - subQuery += ` INNER JOIN ${dbname}.dbo.sources ON ${dbname}.dbo.sources.id=${dbname}.dbo.${db}._from`; - } - subWhere = ''; - if (options.id) { - subWhere += ` ${dbname}.dbo.${db}.id=${options.id}`; - } - if (options.ignoreNull) { - //subWhere += (subWhere ? ` AND` : '') + ` val <> NULL`; - } - subWhere += `${subWhere ? ` AND` : ''} ${dbname}.dbo.${db}.ts < ` + options.start; - if (subWhere) { - subQuery += ` WHERE ${subWhere}`; - } - subQuery += ` ORDER BY ${dbname}.dbo.${db}.ts DESC`; - where += ` UNION ALL SELECT * FROM (${subQuery}) a`; - - // add next value after end - subQuery = ` SELECT TOP 1 ts, val` + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? `, ack` : '') + - (options.from ? `, ${dbname}.dbo.sources.name as 'from'` : '') + - (options.q ? `, q` : '') + ` FROM ${dbname}.dbo.${db}`; - if (options.from) { - subQuery += ` INNER JOIN ${dbname}.dbo.sources ON ${dbname}.dbo.sources.id=${dbname}.dbo.${db}._from`; - } - subWhere = ''; - if (options.id) { - subWhere += ` ${dbname}.dbo.${db}.id=${options.id}`; - } - if (options.ignoreNull) { - //subWhere += (subWhere ? ` AND` : '') + ` val <> NULL`; - } - subWhere += `${subWhere ? ` AND` : ''} ${dbname}.dbo.${db}.ts >= ${options.end}`; - if (subWhere) { - subQuery += ` WHERE ${subWhere}`; - } - subQuery += ` ORDER BY ${dbname}.dbo.${db}.ts ASC`; - where += ` UNION ALL SELECT * FROM (${subQuery}) b`; - } - - if (where) query += ` WHERE ` + where; - - query += ` ORDER BY ts`; - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count && options.returnNewestEntries) ) { - query += ` DESC`; - } else { - query += ` ASC`; - } - query += `;`; - - return query; -}; - -exports.delete = function (dbname, db, index, start, end) { - let query = `DELETE FROM ${dbname}.dbo.${db} WHERE`; - query += ` id=${index}`; - if (start && end) { - query += ` AND ${dbname}.dbo.${db}.ts>=${start} AND ${dbname}.dbo.${db}.ts<=${end}`; - } else if (start) { - query += ` AND ${dbname}.dbo.${db}.ts=${start}`; - } - - query += ";"; - - return query; -}; - -exports.update = function (dbname, index, state, from, db) { - if (!state || state.val === null || state.val === undefined) { - state.val = 'NULL'; - } else if (db === 'ts_bool') { - state.val = state.val ? 1 : 0; - }else if (db === 'ts_string') { - state.val = `'${state.val.toString().replace(/'/g, '')}'`; - } - - let query = `UPDATE ${dbname}.dbo.${db} SET `; - let vals = []; - if (state.val !== undefined) { - vals.push(`val=${state.val}`); - } - if (state.q !== undefined) { - vals.push(`q=${state.q}`); - } - if (from !== undefined) { - vals.push(`_from=${from}`); - } - if (state.ack !== undefined) { - vals.push(`ack=${state.ack ? 1 : 0}`); - } - query += vals.join(', '); - query += ' WHERE '; - query += ` id=${index}`; - query += ` AND ts=${state.ts}`; - query += ';'; - - return query; -}; diff --git a/lib/mysql-client.js b/lib/mysql-client.js deleted file mode 100644 index 5fe9723e..00000000 --- a/lib/mysql-client.js +++ /dev/null @@ -1,62 +0,0 @@ -// Generated by CoffeeScript 2.6.0 -(function() { - const ConnectionFactory = require('sql-client/lib/connection-factory').ConnectionFactory; - const SQLClient = require('sql-client/lib/sql-client').SQLClient; - const SQLClientPool = require('sql-client/lib/sql-client-pool').SQLClientPool; - const mysql2 = require('mysql2'); - - const MySQL2ConnectionFactory = class MySQL2ConnectionFactory extends ConnectionFactory { - constructor() { - super(...arguments); - this.open_connection = this.open_connection.bind(this); - this.close_connection = this.close_connection.bind(this); - } - - open_connection(options, callback) { - const opt = Object.assign({}, options); - delete opt.max_idle; - delete opt.max_active; - delete opt.max_wait; - delete opt.when_exhausted; - delete opt.options; - delete opt.server; - - let connection = mysql2.createConnection(opt); - return connection.connect((err) => { - return callback(err, connection); - }); - } - - close_connection(connection, callback) { - if (typeof (connection != null ? connection.end : void 0) === 'function') { - return connection.end(callback); - } else { - return super.close_connection(connection, callback); - } - } - - }; - - const MySQL2Client = class MySQL2Client extends SQLClient { - constructor(...options) { - - super(...options, new MySQL2ConnectionFactory()); - } - - }; - - const MySQL2ClientPool = class MySQL2ClientPool extends SQLClientPool { - constructor(...options) { - - super(...options, new MySQL2ConnectionFactory()); - } - - }; - - exports.MySQL2ConnectionFactory = MySQL2ConnectionFactory; - - exports.MySQL2Client = MySQL2Client; - - exports.MySQL2ClientPool = MySQL2ClientPool; - -}).call(this); diff --git a/lib/mysql.js b/lib/mysql.js deleted file mode 100644 index 3ea0c829..00000000 --- a/lib/mysql.js +++ /dev/null @@ -1,263 +0,0 @@ -exports.init = function (dbname, doNotCreateDatabase) { - const commands = [ - `CREATE TABLE \`${dbname}\`.sources (id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, name TEXT);`, - `CREATE TABLE \`${dbname}\`.datapoints (id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, name TEXT, type INTEGER);`, - `CREATE TABLE \`${dbname}\`.ts_number (id INTEGER, ts BIGINT, val REAL, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));`, - `CREATE TABLE \`${dbname}\`.ts_string (id INTEGER, ts BIGINT, val TEXT, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));`, - `CREATE TABLE \`${dbname}\`.ts_bool (id INTEGER, ts BIGINT, val BOOLEAN, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));`, - `CREATE TABLE \`${dbname}\`.ts_counter (id INTEGER, ts BIGINT, val REAL);` - ]; - - !doNotCreateDatabase && commands.unshift(`CREATE DATABASE \`${dbname}\` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;`); - - return commands; -}; - -exports.destroy = function (dbname) { - return [ - `DROP TABLE \`${dbname}\`.ts_counter;`, - `DROP TABLE \`${dbname}\`.ts_number;`, - `DROP TABLE \`${dbname}\`.ts_string;`, - `DROP TABLE \`${dbname}\`.ts_bool;`, - `DROP TABLE \`${dbname}\`.sources;`, - `DROP TABLE \`${dbname}\`.datapoints;`, - `DROP DATABASE \`${dbname}\`;` - ]; -}; - -exports.getFirstTs = function (dbname, db) { - return `SELECT id, MIN(ts) AS ts FROM \`${dbname}\`.${db} GROUP BY id;`; -}; - -exports.insert = function (dbname, index, values) { - const insertValues = {} - values.forEach(value => { - // state, from, db - insertValues[value.db] = insertValues[value.db] || []; - - if (!value.state || value.state.val === null || value.state.val === undefined) { - value.state.val = 'NULL'; - } else if (value.db === 'ts_string') { - value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; - } else if (value.db === 'ts_number') { - if (isNaN(value.state.val)) { - value.state.val = 'NULL'; - } - } - - if (value.db === 'ts_counter') { - insertValues[value.db].push(`(${index}, ${value.state.ts}, ${value.state.val})`); - } else { - insertValues[value.db].push(`(${index}, ${value.state.ts}, ${value.state.val}, ${(value.state.ack ? 1 : 0)}, ${value.from || 0}, ${value.state.q || 0})`); - } - }); - - let query = ''; - for (let db in insertValues) { - if (db === 'ts_counter') { - while (insertValues[db].length) { - query += `INSERT INTO \`${dbname}\`.ts_counter (id, ts, val) VALUES ${insertValues[db].splice(0, 500).join(',')};`; - } - } else { - while (insertValues[db].length) { - query += `INSERT INTO \`${dbname}\`.${db} (id, ts, val, ack, _from, q) VALUES ${insertValues[db].splice(0, 500).join(',')};`; - } - } - } - - return query; -} - -exports.retention = function (dbname, index, db, retention) { - const d = new Date(); - d.setSeconds(-retention); - let query = `DELETE FROM \`${dbname}\`.${db} WHERE`; - query += ` id=${index}`; - query += ` AND ts < ${d.getTime()}`; - query += ';'; - - return query; -}; - -exports.getIdSelect = function (dbname, name) { - if (!name) { - return `SELECT id, type, name FROM \`${dbname}\`.datapoints;`; - } else { - return `SELECT id, type, name FROM \`${dbname}\`.datapoints WHERE name='${name}';`; - } -}; - -exports.getIdInsert = function (dbname, name, type) { - return `INSERT INTO \`${dbname}\`.datapoints (name, type) VALUES('${name}', ${type});`; -}; - -exports.getIdUpdate = function (dbname, id, type) { - return `UPDATE \`${dbname}\`.datapoints SET type=${type} WHERE id=${id};`; -}; - -exports.getFromSelect = function (dbname, from) { - if (from) { - return `SELECT id FROM \`${dbname}\`.sources WHERE name='${from}';`; - } else { - return `SELECT id, name FROM \`${dbname}\`.sources;`; - } -}; - -exports.getFromInsert = function (dbname, from) { - return `INSERT INTO \`${dbname}\`.sources (name) VALUES('${from}');`; -}; - -exports.getCounterDiff = function (dbname, options) { - // Take first real value after start - const subQueryStart = "SELECT ts, val FROM `" + dbname + "`.ts_number WHERE id=" + options.id + " AND ts>=" + options.start + " AND ts<" + options.end + " AND val IS NOT NULL ORDER BY ts ASC LIMIT 1"; - // Take last real value before end - const subQueryEnd = "SELECT ts, val FROM `" + dbname + "`.ts_number WHERE id=" + options.id + " AND ts>=" + options.start + " AND ts<" + options.end + " AND val IS NOT NULL ORDER BY ts DESC LIMIT 1"; - // Take last value before start - const subQueryFirst = "SELECT ts, val FROM `" + dbname + "`.ts_number WHERE id=" + options.id + " AND ts< " + options.start + " ORDER BY ts DESC LIMIT 1"; - // Take next value after end - const subQueryLast = "SELECT ts, val FROM `" + dbname + "`.ts_number WHERE id=" + options.id + " AND ts>= " + options.end + " ORDER BY ts ASC LIMIT 1"; - // get values from counters where counter changed from up to down (e.g. counter changed) - const subQueryCounterChanges = "SELECT ts, val FROM `" + dbname + "`.ts_counter WHERE id=" + options.id + " AND ts>" + options.start + " AND ts<" + options.end + " AND val IS NOT NULL ORDER BY ts ASC"; - - return "SELECT DISTINCT(a.ts), a.val from ((" + subQueryFirst + ")\n" + - "UNION ALL \n(" + subQueryStart + ")\n" + - "UNION ALL \n(" + subQueryEnd + ")\n" + - "UNION ALL \n(" + subQueryLast + ")\n" + - "UNION ALL \n(" + subQueryCounterChanges + ")\n" + - "ORDER BY ts) a;"; -}; - -exports.getHistory = function (dbname, db, options) { - let query = 'SELECT ts, val' + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? ', ack' : '') + - (options.from ? `, \`${dbname}\`.sources.name as 'from'` : '') + - (options.q ? ', q' : '') + " FROM `" + dbname + "`." + db; - - if (options.from) { - query += ` INNER JOIN \`${dbname}\`.sources ON \`${dbname}\`.sources.id=\`${dbname}\`.${db}._from`; - } - - let where = ''; - - if (options.id) { - where += ` \`${dbname}\`.${db}.id=${options.id}`; - } - if (options.end) { - where += `${where ? ' AND' : ''} \`${dbname}\`.${db}.ts < ${options.end}`; - } - if (options.start) { - where += `${where ? ' AND' : ''} \`${dbname}\`.${db}.ts >= ${options.start}`; - - let subQuery; - let subWhere; - subQuery = ' SELECT ts, val' + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? ', ack' : '') + - (options.from ? `, \`${dbname}\`.sources.name as 'from'` : '') + - (options.q ? ', q' : '') + ' FROM `' + dbname + '`.' + db; - if (options.from) { - subQuery += ` INNER JOIN \`${dbname}\`.sources ON \`${dbname}\`.sources.id=\`${dbname}\`.${db}._from`; - } - subWhere = ""; - if (options.id) { - subWhere += ` \`${dbname}\`.${db}.id=${options.id}`; - } - if (options.ignoreNull) { - // subWhere += (subWhere ? " AND" : "") + " val <> NULL"; - } - subWhere += `${subWhere ? ' AND' : ''} \`${dbname}\`.${db}.ts < ${options.start}`; - if (subWhere) { - subQuery += ` WHERE ${subWhere}`; - } - subQuery += ` ORDER BY \`${dbname}\`.${db}.ts DESC LIMIT 1`; - where += ` UNION ALL (${subQuery})`; - - // add next value after end - subQuery = " SELECT ts, val" + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? ', ack' : '') + - (options.from ? `, \`${dbname}\`.sources.name as 'from'` : '') + - (options.q ? ', q' : '') + ' FROM `' + dbname + '`.' + db; - if (options.from) { - subQuery += ` INNER JOIN \`${dbname}\`.sources ON \`${dbname}\`.sources.id=\`${dbname}\`.${db}._from`; - } - subWhere = ""; - if (options.id) { - subWhere += ` \`${dbname}\`.${db}.id=${options.id}`; - } - if (options.ignoreNull) { - // subWhere += (subWhere ? " AND" : "") + " val <> NULL"; - } - subWhere += `${subWhere ? ' AND' : ''} \`${dbname}\`.${db}.ts >= ${options.end}`; - if (subWhere) { - subQuery += ` WHERE ${subWhere}`; - } - subQuery += ` ORDER BY \`${dbname}\`.${db}.ts ASC LIMIT 1`; - where += ` UNION ALL (${subQuery})`; - } - - if (where) { - query += ` WHERE ${where}`; - } - - query += ' ORDER BY ts'; - - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count && options.returnNewestEntries) ) { - query += ' DESC'; - } else { - query += ' ASC'; - } - - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { - query += ` LIMIT ${options.count + 2}`; - } - - query += ';'; - return query; -}; - -exports.delete = function (dbname, db, index, start, end) { - let query = `DELETE FROM \`${dbname}\`.${db} WHERE`; - query += ` id=${index}`; - - if (start && end) { - query += ` AND ts>=${start} AND ts<=${end}`; - } else if (start) { - query += ` AND ts=${start}`; - } - - query += ';'; - - return query; -}; - -exports.update = function (dbname, index, state, from, db) { - if (!state || state.val === null || state.val === undefined) { - state.val = 'NULL'; - } else if (db === 'ts_string') { - state.val = `'${state.val.toString().replace(/'/g, '')}'`; - } - - let query = `UPDATE \`${dbname}\`.${db} SET `; - let vals = []; - if (state.val !== undefined) { - vals.push(`val=${state.val}`); - } - if (state.q !== undefined) { - vals.push(`q=${state.q}`); - } - if (from !== undefined) { - vals.push(`_from=${from}`); - } - if (state.ack !== undefined) { - vals.push(`ack=${state.ack ? 1 : 0}`); - } - query += vals.join(', '); - query += ' WHERE '; - query += ` id=${index}`; - query += ` AND ts=${state.ts}`; - query += ';'; - - return query; -}; diff --git a/lib/postgresql-client.js b/lib/postgresql-client.js deleted file mode 100644 index 09aef998..00000000 --- a/lib/postgresql-client.js +++ /dev/null @@ -1,202 +0,0 @@ -// Generated by CoffeeScript 2.3.2 -(function() { - let pg = require('pg'); - const Url = require('url'); - const querystring = require('querystring'); - - const SQLClient = require('sql-client/lib/sql-client').SQLClient; - const SQLClientPool = require('sql-client/lib/sql-client-pool').SQLClientPool; - const ConnectionFactory = require('sql-client/lib/connection-factory').ConnectionFactory; - - try { - if ((pg != null ? pg['native'] : void 0) != null) { - pg = pg['native']; - } - } catch(err) { - - } - - // PostgreSQLConnectionFactory does not use any of node-pg's built-in pooling. - const PostgreSQLConnectionFactory = class PostgreSQLConnectionFactory extends ConnectionFactory { - constructor() { - super(); - this.open_connection = this.open_connection.bind(this); - this.pre_process_sql = this.pre_process_sql.bind(this); - } - - open_connection(connect_string, callback) { - let connection = new pg.Client(connect_string); - return connection.connect((err) => { - return callback(err, connection); - }); - } - - pre_process_sql(sql, bindvars, callback) { - let index; - if ((sql != null) && (bindvars != null)) { - index = 1; - sql = sql.replace(/\?/g, (function() { - return '$' + index++; - })); - } - return callback(null, sql, bindvars); - } - }; - - const PostgreSQLClient = class PostgreSQLClient extends SQLClient { - constructor(...options) { - super(...options, new PostgreSQLConnectionFactory()); - } - - }; - - const PostgreSQLClientPool = class PostgreSQLClientPool extends SQLClientPool { - constructor(...options) { - super(...options, new PostgreSQLConnectionFactory()); - } - - }; - - exports.PostgreSQLConnectionFactory = PostgreSQLConnectionFactory; - exports.PostgreSQLClient = PostgreSQLClient; - exports.PostgreSQLClientPool = PostgreSQLClientPool; - - const PostgreSQLConnectionFactory2 = (function() { - // PostgreSQLConnectionFactory2 DOES usenode-pg's built-in pooling. - class PostgreSQLConnectionFactory2 extends PostgreSQLConnectionFactory { - constructor() { - super(); - this._parse_connect_string = this._parse_connect_string.bind(this); - this.open_connection = this.open_connection.bind(this); - this.close_connection = this.close_connection.bind(this); - this.pg_pools_by_connect_string = {}; - this._connect_string_regexp = /^([^:]+):\/\/([^:]+):([^@]+)@([^:\/]+)(:([0-9]+))?(.*)$/; - } - - _parse_connect_string(connect_string) { - let config; - let matches; - let name; - let parsed_path; - let path; - let qs; - let value; - - if (typeof connect_string === 'string' && this._connect_string_regexp.test(connect_string)) { - matches = connect_string.match(this._connect_string_regexp); - config = {}; - config.database = matches[1]; - config.user = matches[2]; - config.password = matches[3]; - config.host = matches[4]; - if (matches[6] != null) { - config.port = parseInt(matches[6]); - } - path = matches[7]; - parsed_path = Url.parse(path); - config.database = parsed_path.pathname.substring(1); - if (parsed_path.query != null) { - qs = querystring.parse(parsed_path.query); - for (name in qs) { - value = qs[name]; - if (value === 'true') { - value = true; - } else if (value === 'false') { - value = false; - } else if (`${value}` === `${parseInt(value)}`) { - value = parseInt(value); - } - config[name] = value; - } - } - return config; - } else { - return connect_string; - } - } - - open_connection(connect_string, callback) { - let key; - let pg_pool; - - key = connect_string; - if (typeof key !== 'string') { - key = JSON.stringify(key); - } - pg_pool = this.pg_pools_by_connect_string[key]; - if (pg_pool == null) { - pg_pool = new pg.Pool(this._parse_connect_string(connect_string)); - this.pg_pools_by_connect_string[key] = pg_pool; - } - return pg_pool.connect((err, client, done_fn) => { - let connection; - connection = client; - if (connection != null) { - connection._sqlclient_done = done_fn; - connection._pg_pool_key = key; - } - return callback(err, connection); - }); - } - - close_connection(connection, callback) { - if ((connection != null ? connection._sqlclient_done : void 0) != null) { - connection._sqlclient_done(); - return typeof callback === "function" ? callback(null) : void 0; - } else { - return super.close_connection(connection, callback); - } - } - - } - - return PostgreSQLConnectionFactory2; - - }).call(this); - - const PostgreSQLClient2 = class PostgreSQLClient2 extends SQLClient { - constructor(...options) { - super(...options, new PostgreSQLConnectionFactory2()); - } - - }; - - const PostgreSQLClientPool2 = class PostgreSQLClientPool2 extends SQLClientPool { - constructor(...options) { - super(...options, new PostgreSQLConnectionFactory2()); - this.destroy = this.destroy.bind(this); - this.close = this.close.bind(this); - } - - destroy(client, callback) { - if (client != null) { - return client.disconnect(callback); - } else { - return typeof callback === "function" ? callback() : void 0; - } - } - - close(callback) { - return super.close((...args) => { - let key; - let pg_pool; - let pools_to_close; - let ref; - let ref1; - pools_to_close = (ref = this.factory) != null ? ref.pg_pools_by_connect_string : void 0; - ref1 = pools_to_close != null ? pools_to_close : {}; - for (key in ref1) { - pg_pool = ref1[key]; - if (!((pg_pool == null) || (pg_pool.ending || pg_pool.ended))) { - pg_pool.end(); - } - } - return typeof callback === "function" ? callback(...args) : void 0; - }); - } - }; - - exports.PostgreSQLConnectionFactory2 = PostgreSQLConnectionFactory2; - exports.PostgreSQLClient2 = PostgreSQLClient2; - exports.PostgreSQLClientPool2 = PostgreSQLClientPool2; -}).call(this); diff --git a/lib/postgresql.js b/lib/postgresql.js deleted file mode 100644 index 83d39d84..00000000 --- a/lib/postgresql.js +++ /dev/null @@ -1,260 +0,0 @@ -exports.init = function (dbname) { - return [ - 'CREATE TABLE sources (id SERIAL NOT NULL PRIMARY KEY, name TEXT);', - 'CREATE TABLE datapoints (id SERIAL NOT NULL PRIMARY KEY, name TEXT, type INTEGER);', - 'CREATE TABLE ts_number (id INTEGER NOT NULL, ts BIGINT, val REAL, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', - 'CREATE TABLE ts_string (id INTEGER NOT NULL, ts BIGINT, val TEXT, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', - 'CREATE TABLE ts_bool (id INTEGER NOT NULL, ts BIGINT, val BOOLEAN, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', - 'CREATE TABLE ts_counter (id INTEGER NOT NULL, ts BIGINT, val REAL);' - ]; -}; - -exports.destroy = function (dbname) { - return [ - 'DROP TABLE ts_counter;', - 'DROP TABLE ts_number;', - 'DROP TABLE ts_string;', - 'DROP TABLE ts_bool;', - 'DROP TABLE sources;', - 'DROP TABLE datapoints;' - ]; -}; - -exports.getFirstTs = function (dbname, db) { - return `SELECT id, MIN(ts) AS ts FROM ${db} GROUP BY id;`; -}; - -exports.insert = function (dbname, index, values) { - const insertValues = {} - values.forEach(value => { - // state, from, db - insertValues[value.db] = insertValues[value.db] || []; - - if (!value.state || value.state.val === null || value.state.val === undefined) { - value.state.val = 'NULL'; - } else if (value.db === 'ts_string') { - value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; - } else if (value.db === 'ts_number') { - if (isNaN(value.state.val)) { - value.state.val = 'NULL'; - } - } - - if (value.db === 'ts_counter') { - insertValues[value.db].push(`(${index}, ${value.state.ts}, ${value.state.val})`); - } else { - insertValues[value.db].push(`(${index}, ${value.state.ts}, ${value.state.val}, ${!!value.state.ack}, ${value.from || 0}, ${value.state.q || 0})`); - } - }); - - let query = ''; - for (let db in insertValues) { - if (db === 'ts_counter') { - while (insertValues[db].length) { - query += `INSERT INTO ts_counter (id, ts, val) VALUES ${insertValues[db].splice(0, 500).join(',')};`; - } - } else { - while (insertValues[db].length) { - query += `INSERT INTO ${db} (id, ts, val, ack, _from, q) VALUES ${insertValues[db].splice(0, 500).join(',')};`; - } - } - } - - return query; -} - -exports.retention = function (dbname, index, db, retention) { - const d = new Date(); - d.setSeconds(-retention); - let query = `DELETE FROM ${db} WHERE`; - query += ` id=${index}`; - query += ` AND ts < ${d.getTime()}`; - query += ';'; - - return query; -}; - -exports.getIdSelect = function (dbname, name) { - if (!name) { - return "SELECT id, type, name FROM datapoints;"; - } else { - return `SELECT id, type, name FROM datapoints WHERE name='${name}';`; - } -}; - -exports.getIdInsert = function (dbname, name, type) { - return `INSERT INTO datapoints (name, type) VALUES('${name}', ${type});`; -}; - -exports.getIdUpdate = function (dbname, id, type) { - return `UPDATE datapoints SET type = ${type} WHERE id = ${id};`; -}; - -exports.getFromSelect = function (dbname, from) { - if (from) { - return `SELECT id FROM sources WHERE name='${from}';`; - } - else { - return "SELECT id, name FROM sources;"; - } -}; - -exports.getFromInsert = function (dbname, from) { - return `INSERT INTO sources (name) VALUES('${from}');`; -}; - -exports.getCounterDiff = function (dbname, options) { - // Take first real value after start - const subQueryStart = `SELECT ts, val FROM \`${dbname}\`.ts_number WHERE id=${options.id} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC LIMIT 1`; - // Take last real value before end - const subQueryEnd = `SELECT ts, val FROM \`${dbname}\`.ts_number WHERE id=${options.id} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts DESC LIMIT 1`; - // Take last value before start - const subQueryFirst = `SELECT ts, val FROM \`${dbname}\`.ts_number WHERE id=${options.id} AND ts< ${options.start} ORDER BY ts DESC LIMIT 1`; - // Take next value after end - const subQueryLast = `SELECT ts, val FROM \`${dbname}\`.ts_number WHERE id=${options.id} AND ts>= ${options.end} ORDER BY ts ASC LIMIT 1`; - // get values from counters where counter changed from up to down (e.g. counter changed) - const subQueryCounterChanges = `SELECT ts, val FROM \`${dbname}\`.ts_counter WHERE id=${options.id} AND ts>${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC`; - - return "SELECT DISTINCT(a.ts), a.val from ((" + subQueryFirst + ")\n" + - "UNION ALL \n(" + subQueryStart + ")\n" + - "UNION ALL \n(" + subQueryEnd + ")\n" + - "UNION ALL \n(" + subQueryLast + ")\n" + - "UNION ALL \n(" + subQueryCounterChanges + ")\n" + - "ORDER BY ts) a;"; -}; - -exports.getHistory = function (dbname, db, options) { - let query = "SELECT ts, val" + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? ', ack' : '') + - (options.from ? ', sources.name as from' : '') + - (options.q ? ', q' : '') + ' FROM ' + db; - - if (options.from) { - query += ` INNER JOIN sources ON sources.id=${db}._from`; - } - - let where = ''; - - if (options.id) { - where += ` ${db}.id=${options.id}`; - } - if (options.end) { - where += `${where ? " AND" : ''} ${db}.ts < ${options.end}`; - } - if (options.start) { - where += `${where ? " AND" : ''} ${db}.ts >= ${options.start}`; - - //add last value before start - let subQuery; - let subWhere; - subQuery = " SELECT ts, val" + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? ', ack' : '') + - (options.from ? ", sources.name as from" : '') + - (options.q ? ', q' : '') + ' FROM ' + db; - if (options.from) { - subQuery += ` INNER JOIN sources ON sources.id=${db}._from`; - } - subWhere = ''; - if (options.id) { - subWhere += ` ${db}.id=${options.id}`; - } - if (options.ignoreNull) { - //subWhere += (subWhere ? " AND" : '') + " val <> NULL"; - } - subWhere += `${subWhere ? " AND" : ''} ${db}.ts < ${options.start}`; - if (subWhere) { - subQuery += ` WHERE ${subWhere}`; - } - subQuery += ` ORDER BY ${db}.ts DESC LIMIT 1`; - where += ` UNION ALL (${subQuery})`; - - //add next value after end - subQuery = " SELECT ts, val" + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? ', ack' : '') + - (options.from ? ", sources.name as from" : '') + - (options.q ? ', q' : '') + ' FROM ' + db; - if (options.from) { - subQuery += ` INNER JOIN sources ON sources.id=${db}._from`; - } - subWhere = ''; - if (options.id) { - subWhere += ` ${db}.id=${options.id}`; - } - if (options.ignoreNull) { - //subWhere += (subWhere ? " AND" : '') + " val <> NULL"; - } - subWhere += `${subWhere ? ' AND' : ''} ${db}.ts >= ${options.end}`; - if (subWhere) { - subQuery += ` WHERE ${subWhere}`; - } - subQuery += ` ORDER BY ${db}.ts ASC LIMIT 1`; - where += ` UNION ALL(${subQuery})`; - } - - if (where) { - query += ` WHERE ${where}`; - } - - query += ' ORDER BY ts'; - - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count && options.returnNewestEntries) ) { - query += ' DESC'; - } else { - query += ' ASC'; - } - - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { - query += ` LIMIT ${options.count + 2}`; - } - - query += ';'; - return query; -}; - -exports.delete = function (dbname, db, index, start, end) { - let query = `DELETE FROM ${db} WHERE`; - query += " id=" + index; - - if (start && end) { - query += ` AND ts>=${start} AND ts <= ${end}`; - } else if (start) { - query += ` AND ts=${start}`; - } - - query += ';'; - - return query; -}; - -exports.update = function (dbname, index, state, from, db) { - if (!state || state.val === null || state.val === undefined) { - state.val = 'NULL'; - } else if (db === 'ts_string') { - state.val = `'${state.val.toString().replace(/'/g, '')}'`; - } - - let query = `UPDATE ${db} SET `; - let vals = []; - if (state.val !== undefined) { - vals.push(`val=${state.val}`); - } - if (state.q !== undefined) { - vals.push(`q=${state.q}`); - } - if (from !== undefined) { - vals.push(`_from=${from}`); - } - if (state.ack !== undefined) { - vals.push(`ack=${!!state.ack}`); - } - query += vals.join(', '); - query += ' WHERE '; - query += ` id=${index}`; - query += ` AND ts=${state.ts}`; - query += ';'; - - return query; -}; diff --git a/lib/sqlite.js b/lib/sqlite.js deleted file mode 100644 index 5e668ebd..00000000 --- a/lib/sqlite.js +++ /dev/null @@ -1,260 +0,0 @@ -exports.init = function (dbname) { - return [ - 'CREATE TABLE sources (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT);', - 'CREATE TABLE datapoints (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT, type INTEGER);', - 'CREATE TABLE ts_number (id INTEGER, ts INTEGER, val REAL, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', - 'CREATE TABLE ts_string (id INTEGER, ts INTEGER, val TEXT, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', - 'CREATE TABLE ts_bool (id INTEGER, ts INTEGER, val BOOLEAN, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', - 'CREATE TABLE ts_counter (id INTEGER, ts INTEGER, val REAL, PRIMARY KEY(id, ts));' - ]; -}; - -exports.destroy = function (dbname) { - return [ - 'DROP TABLE ts_counter;', - 'DROP TABLE ts_number;', - 'DROP TABLE ts_string;', - 'DROP TABLE ts_bool;', - 'DROP TABLE sources;', - 'DROP TABLE datapoints;' - ]; -}; - -exports.getFirstTs = function (dbname, db) { - return `SELECT id, MIN(ts) AS ts FROM ${db} GROUP BY id;`; -}; - -exports.insert = function (dbname, index, values) { - const insertValues = {} - values.forEach(value => { - // state, from, db - insertValues[value.db] = insertValues[value.db] || []; - - if (!value.state || value.state.val === null || value.state.val === undefined) { - value.state.val = 'NULL'; - } else if (value.db === 'ts_bool') { - value.state.val = value.state.val ? 1 : 0; - } else if (value.db === 'ts_string') { - value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; - } else if (value.db === 'ts_number') { - if (isNaN(value.state.val)) { - value.state.val = 'NULL'; - } - } - - if (value.db === 'ts_counter') { - insertValues[value.db].push(`(${index}, ${value.state.ts}, ${value.state.val})`); - } else { - insertValues[value.db].push(`(${index}, ${value.state.ts}, ${value.state.val}, ${(value.state.ack ? 1 : 0)}, ${value.from || 0}, ${value.state.q || 0})`); - } - }); - - let query = ''; - for (let db in insertValues) { - if (db === 'ts_counter') { - while (insertValues[db].length) { - query += `INSERT INTO ts_counter (id, ts, val) VALUES ${insertValues[db].splice(0, 500).join(',')};`; - } - } else { - while (insertValues[db].length) { - query += `INSERT INTO ${db} (id, ts, val, ack, _from, q) VALUES ${insertValues[db].splice(0, 500).join(',')};`; - } - } - } - - return query; -} - -exports.retention = function (dbname, index, db, retention) { - const d = new Date(); - d.setSeconds(-retention); - let query = `DELETE FROM ${db} WHERE`; - query += ` id=${index}`; - query += ` AND ts < ${d.getTime()}`; - query += ';'; - return query; -}; - -exports.getIdSelect = function (dbname, name) { - if (!name) { - return 'SELECT id, type, name FROM datapoints;'; - } else { - return `SELECT id, type, name FROM datapoints WHERE name='${name}';`; - } -}; - -exports.getIdInsert = function (dbname, name, type) { - return `INSERT INTO datapoints (name, type) VALUES('${name}', ${type});`; -}; - -exports.getIdUpdate = function (dbname, id, type) { - return `UPDATE datapoints SET type = ${type} WHERE id = ${id};`; -}; - -exports.getFromSelect = function (dbname, from) { - if (!from) { - return 'SELECT id, name FROM sources;'; - } else { - return `SELECT id FROM sources WHERE name='${from}';`; - } -}; - -exports.getFromInsert = function (dbname, from) { - return `INSERT INTO sources (name) VALUES('${from}');`; -}; - -exports.getCounterDiff = function (dbname, options) { - // Take first real value after start - const subQueryStart = `SELECT ts, val FROM ts_number WHERE id=${options.id} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC LIMIT 1`; - // Take last real value before end - const subQueryEnd = `SELECT ts, val FROM ts_number WHERE id=${options.id} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts DESC LIMIT 1`; - // Take last value before start - const subQueryFirst = `SELECT ts, val FROM ts_number WHERE id=${options.id} AND ts< ${options.start} ORDER BY ts DESC LIMIT 1`; - // Take next value after end - const subQueryLast = `SELECT ts, val FROM ts_number WHERE id=${options.id} AND ts>= ${options.end} ORDER BY ts ASC LIMIT 1`; - // get values from counters where counter changed from up to down (e.g. counter changed) - const subQueryCounterChanges = `SELECT ts, val FROM ts_counter WHERE id=${options.id} AND ts>${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC`; - - return "SELECT DISTINCT(a.ts), a.val from ((" + subQueryFirst + ")\n" + - "UNION ALL \n(" + subQueryStart + ")\n" + - "UNION ALL \n(" + subQueryEnd + ")\n" + - "UNION ALL \n(" + subQueryLast + ")\n" + - "UNION ALL \n(" + subQueryCounterChanges + ")\n" + - "ORDER BY ts) a;"; -}; - -exports.getHistory = function (dbname, db, options) { - let query = 'SELECT ts, val' + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? ', ack' : '') + - (options.from ? `, sources.name as 'from'` : '') + - (options.q ? ', q' : '') + ' FROM ' + db; - - if (options.from) { - query += ` INNER JOIN sources ON sources.id=${db}._from`; - } - - let where = ''; - - if (options.id) { - where += ` ${db}.id=${options.id}`; - } - if (options.end) { - where += `${where ? ' AND' : ''} ${db}.ts < ${options.end}`; - } - if (options.start) { - where += `${where ? ' AND' : ''} ${db}.ts >= ${options.start}`; - - //add last value before start - let subQuery; - let subWhere; - subQuery = " SELECT ts, val" + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? ', ack' : '') + - (options.from ? `, sources.name as 'from'` : '') + - (options.q ? ', q' : '') + " FROM " + db; - if (options.from) { - subQuery += ` INNER JOIN sources ON sources.id=${db}._from`; - } - subWhere = ''; - if (options.id) { - subWhere += ` ${db}.id=${options.id}`; - } - if (options.ignoreNull) { - // subWhere += (subWhere ? " AND" : '') + " val <> NULL"; - } - subWhere += `${subWhere ? ' AND' : ''} ${db}.ts < ${options.start}`; - if (subWhere) { - subQuery += ` WHERE ${subWhere}`; - } - subQuery += ` ORDER BY ${db}.ts DESC LIMIT 1`; - where += ` UNION ALL SELECT * from (${subQuery})`; - - //add next value after end - subQuery = " SELECT ts, val" + - (!options.id ? `, ${db}.id as id` : '') + - (options.ack ? ", ack" : '') + - (options.from ? `, sources.name as 'from'` : '') + - (options.q ? ', q' : '') + ' FROM ' + db; - if (options.from) { - subQuery += ` INNER JOIN sources ON sources.id=${db}._from`; - } - subWhere = ''; - if (options.id) { - subWhere += ` ${db}.id=${options.id}`; - } - if (options.ignoreNull) { - //subWhere += (subWhere ? " AND" : '') + " val <> NULL"; - } - subWhere += `${subWhere ? ' AND' : ''} ${db}.ts >= ${options.end}`; - if (subWhere) { - subQuery += ` WHERE ${subWhere}`; - } - subQuery += ` ORDER BY ${db}.ts ASC LIMIT 1`; - where += ` UNION ALL SELECT * from (${subQuery}) `; - } - - if (where) { - query += ` WHERE ${where}`; - } - - query += ' ORDER BY ts'; - - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count && options.returnNewestEntries) ) { - query += ' DESC'; - } else { - query += ' ASC'; - } - - if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { - query += ` LIMIT ${options.count + 2}`; - } - - query += ";"; - return query; -}; - -exports.delete = function (dbname, db, index, start, end) { - let query = `DELETE FROM ${db} WHERE`; - query += ` id=${index}`; - - if (start && end) { - query += ` AND ts>=${start} AND ts <= ${end}`; - } else if (start) { - query += ` AND ts=${start}`; - } - - query += ';'; - - return query; -}; - -exports.update = function (dbname, index, state, from, db) { - if (!state || state.val === null || state.val === undefined) { - state.val = 'NULL'; - } else if (db === 'ts_string') { - state.val = `'${state.val.toString().replace(/'/g, '')}'`; - } - - let query = `UPDATE ${db} SET `; - let vals = []; - if (state.val !== undefined) { - vals.push(`val=${state.val}`); - } - if (state.q !== undefined) { - vals.push(`q=${state.q}`); - } - if (from !== undefined) { - vals.push(`_from=${from}`); - } - if (state.ack !== undefined) { - vals.push(`ack=${state.ack ? 1 : 0}`); - } - query += vals.join(', '); - query += ' WHERE '; - query += ` id=${index}`; - query += ` AND ts=${state.ts}`; - query += ';'; - - return query; -}; diff --git a/lib/tools.js b/lib/tools.js deleted file mode 100644 index ec0ad329..00000000 --- a/lib/tools.js +++ /dev/null @@ -1,99 +0,0 @@ -const axios = require('axios').default; - -/** - * Tests whether the given variable is a real object and not an Array - * @param {any} it The variable to test - * @returns {it is Record} - */ -function isObject(it) { - // This is necessary because: - // typeof null === 'object' - // typeof [] === 'object' - // [] instanceof Object === true - return Object.prototype.toString.call(it) === '[object Object]'; -} - -/** - * Tests whether the given variable is really an Array - * @param {any} it The variable to test - * @returns {it is any[]} - */ -function isArray(it) { - if (typeof Array.isArray === 'function') return Array.isArray(it); - return Object.prototype.toString.call(it) === '[object Array]'; -} - -/** - * Translates text to the target language. Automatically chooses the right translation API. - * @param {string} text The text to translate - * @param {string} targetLang The target languate - * @param {string} [yandexApiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers - * @returns {Promise} - */ -async function translateText(text, targetLang, yandexApiKey) { - if (targetLang === 'en') { - return text; - } else if (!text) { - return ''; - } - if (yandexApiKey) { - return translateYandex(text, targetLang, yandexApiKey); - } else { - return translateGoogle(text, targetLang); - } -} - -/** - * Translates text with Yandex API - * @param {string} text The text to translate - * @param {string} targetLang The target languate - * @param {string} apiKey The yandex API key. You can create one for free at https://translate.yandex.com/developers - * @returns {Promise} - */ -async function translateYandex(text, targetLang, apiKey) { - if (targetLang === 'zh-cn') { - targetLang = 'zh'; - } - try { - const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; - const response = await axios({url, timeout: 15000}); - if (response.data && response.data.text && isArray(response.data.text)) { - return response.data.text[0]; - } - throw new Error('Invalid response for translate request'); - } catch (e) { - throw new Error(`Could not translate to "${targetLang}": ${e}`); - } -} - -/** - * Translates text with Google API - * @param {string} text The text to translate - * @param {string} targetLang The target languate - * @returns {Promise} - */ -async function translateGoogle(text, targetLang) { - try { - const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; - const response = await axios({url, timeout: 15000}); - if (isArray(response.data)) { - // we got a valid response - return response.data[0][0][0]; - } - throw new Error('Invalid response for translate request'); - } catch (e) { - if (e.response && e.response.status === 429) { - throw new Error( - `Could not translate to "${targetLang}": Rate-limited by Google Translate` - ); - } else { - throw new Error(`Could not translate to "${targetLang}": ${e}`); - } - } -} - -module.exports = { - isArray, - isObject, - translateText -}; diff --git a/main.js b/main.js deleted file mode 100644 index 7797ee1b..00000000 --- a/main.js +++ /dev/null @@ -1,3469 +0,0 @@ -/* jshint -W097 */ -/* jshint strict: false */ -/* jslint node: true */ -'use strict'; - -const utils = require('@iobroker/adapter-core'); // Get common adapter utils -const adapterName = require('./package.json').name.split('.').pop(); -const SQL = require('sql-client'); -const commons = require('./lib/aggregate'); -const fs = require('fs'); -const path = require('path'); -let SQLFuncs = null; - -const clients = { - postgresql: {name: 'PostgreSQLClient', multiRequests: true}, - mysql: {name: 'MySQL2Client', multiRequests: true}, - sqlite: {name: 'SQLite3Client', multiRequests: false}, - mssql: {name: 'MSSQLClient', multiRequests: true} -}; - -const types = { - number: 0, - string: 1, - boolean: 2, - object: 1, -}; - -const dbNames = [ - 'ts_number', - 'ts_string', - 'ts_bool', -]; - -const storageTypes = [ - 'Number', - 'String', - 'Boolean', -]; - -const sqlDPs = {}; -const from = {}; -const MAXTASKS = 100; -const tasks = []; -const tasksReadType = []; -const tasksStart = []; -const isFromRunning = {}; -const aliasMap = {}; - -let finished = false; -let connected = null; -let multiRequests = true; -let subscribeAll = false; -let clientPool; -let reconnectTimeout = null; -let testConnectTimeout = null; -let dpOverviewTimeout = null; -let bufferChecker = null; -let activeConnections = 0; -let poolBorrowGuard = []; // required until https://github.com/intellinote/sql-client/issues/7 is fixed -const logConnectionUsage = true; - -function isEqual(a, b) { - //console.log('Compare ' + JSON.stringify(a) + ' with ' + JSON.stringify(b)); - // Create arrays of property names - if (a === null || a === undefined || b === null || b === undefined) { - return a === b; - } - - const aProps = Object.getOwnPropertyNames(a); - const bProps = Object.getOwnPropertyNames(b); - - // If number of properties is different, - // objects are not equivalent - if (aProps.length !== bProps.length) { - //console.log('num props different: ' + JSON.stringify(aProps) + ' / ' + JSON.stringify(bProps)); - return false; - } - - for (let i = 0; i < aProps.length; i++) { - const propName = aProps[i]; - - if (typeof a[propName] !== typeof b[propName]) { - //console.log('type props ' + propName + ' different'); - return false; - } else - if (typeof a[propName] === 'object') { - if (!isEqual(a[propName], b[propName])) { - return false; - } - } else { - // If values of same property are not equal, - // objects are not equivalent - if (a[propName] !== b[propName]) { - //console.log('props ' + propName + ' different'); - return false; - } - } - } - - // If we made it this far, objects - // are considered equivalent - return true; -} - -let adapter; -function startAdapter(options) { - options = options || {}; - - Object.assign(options, {name: adapterName}); - - adapter = new utils.Adapter(options); - - adapter.on('objectChange', (id, obj) => { - let tmpState; - const now = Date.now(); - const formerAliasId = aliasMap[id] ? aliasMap[id] : id; - - if (obj && obj.common && obj.common.custom && obj.common.custom[adapter.namespace] && typeof obj.common.custom[adapter.namespace] === 'object' && obj.common.custom[adapter.namespace].enabled) { - const realId = id; - let checkForRemove = true; - - if (obj.common.custom && obj.common.custom[adapter.namespace] && obj.common.custom[adapter.namespace].aliasId) { - if (obj.common.custom[adapter.namespace].aliasId !== id) { - aliasMap[id] = obj.common.custom[adapter.namespace].aliasId; - adapter.log.debug(`Registered Alias: ${id} --> ${aliasMap[id]}`); - id = aliasMap[id]; - checkForRemove = false; - } else { - adapter.log.warn(`Ignoring Alias-ID because identical to ID for ${id}`); - obj.common.custom[adapter.namespace].aliasId = ''; - } - } - - if (checkForRemove && aliasMap[id]) { - adapter.log.debug(`Removed Alias: ${id} !-> ${aliasMap[id]}`); - delete aliasMap[id]; - } - - if (!(sqlDPs[formerAliasId] && sqlDPs[formerAliasId][adapter.namespace]) && !subscribeAll) { - // un-subscribe - for (const _id in sqlDPs) { - if (sqlDPs.hasOwnProperty(_id) && sqlDPs.hasOwnProperty(sqlDPs[_id].realId)) { - adapter.unsubscribeForeignStates(sqlDPs[_id].realId); - } - } - subscribeAll = true; - adapter.subscribeForeignStates('*'); - } - - if (sqlDPs[id] && sqlDPs[id].index === undefined) { - getId(id, sqlDPs[id].dbtype, () => reInit(id, realId, formerAliasId, obj)); - } else { - reInit(id, realId, formerAliasId, obj); - } - } else { - if (aliasMap[id]) { - adapter.log.debug(`Removed Alias: ${id} !-> ${aliasMap[id]}`); - delete aliasMap[id]; - } - - id = formerAliasId; - - if (sqlDPs[id] && sqlDPs[id][adapter.namespace]) { - adapter.log.info(`disabled logging of ${id}`); - if (sqlDPs[id].relogTimeout) { - clearTimeout(sqlDPs[id].relogTimeout); - sqlDPs[id].relogTimeout = null; - } - if (sqlDPs[id].timeout) { - clearTimeout(sqlDPs[id].timeout); - sqlDPs[id].timeout = null; - } - - tmpState = Object.assign({}, sqlDPs[id].state); - const state = sqlDPs[id].state ? tmpState : null; - - if (sqlDPs[id][adapter.namespace]) { - sqlDPs[id][adapter.namespace].enabled = false; - if (sqlDPs[id].skipped && !sqlDPs[id][adapter.namespace].disableSkippedValueLogging) { - pushValueIntoDB(id, sqlDPs[id].skipped, false, true); - sqlDPs[id].skipped = null; - } - - if (adapter.config.writeNulls) { - const nullValue = { - val: null, - ts: now, - lc: now, - q: 0x40, - from: `system.adapter.${adapter.namespace}` - }; - - if (sqlDPs[id][adapter.namespace].changesOnly && state && state.val !== null) { - (function (_id, _state, _nullValue) { - _state.ts = now; - _state.from = `system.adapter.${adapter.namespace}`; - nullValue.ts += 4; - nullValue.lc += 4; // because of MS SQL - adapter.log.debug(`Write 1/2 "${_state.val}" _id: ${_id}`); - pushValueIntoDB(_id, _state, false, true, () => { - // terminate values with null to indicate adapter stop. timestamp + 1 - adapter.log.debug(`Write 2/2 "null" _id: ${_id}`); - pushValueIntoDB(_id, _nullValue, () => delete sqlDPs[id]); - }); - })(id, state, nullValue); - } else { - // terminate values with null to indicate adapter stop. timestamp + 1 - adapter.log.debug(`Write 0 NULL _id: ${id}`); - pushValueIntoDB(id, nullValue, () => delete sqlDPs[id]); - } - } else { - storeCached(id, () => delete sqlDPs[id]); - } - } else { - delete sqlDPs[id]; - } - } - } - }); - - adapter.on('stateChange', (id, state) => { - id = aliasMap[id] ? aliasMap[id] : id; - pushHistory(id, state); - }); - - adapter.on('unload', callback => finish(callback)); - - adapter.on('ready', () => main()); - - adapter.on('message', msg => processMessage(msg)); - - return adapter; -} - -function borrowClientFromPool(callback) { - if (typeof callback !== 'function') { - return; - } - - if (!clientPool) { - setConnected(false); - return callback('No database connection'); - } - setConnected(true); - - if (activeConnections >= adapter.config.maxConnections) { - logConnectionUsage && adapter.log.debug(`Borrow connection not possible: ${activeConnections} >= Max - Store for Later`); - poolBorrowGuard.push(callback); - return; - } - activeConnections++; - logConnectionUsage && adapter.log.debug(`Borrow connection from pool: ${activeConnections} now`); - clientPool.borrow((err, client) => { - if (!err && client) { - // make sure we always have at least one error listener to prevent crashes - if (client.on && client.listenerCount && client.listenerCount('error') === 0) { - client.on('error', err => - adapter.log.warn(`SQL client error: ${err}`)); - } - } else if (!client) { - activeConnections--; - } - - callback(err, client); - }); -} - -function returnClientToPool(client) { - if (client) { - activeConnections--; - } - logConnectionUsage && adapter.log.debug(`Return connection to pool: ${activeConnections} now`); - if (clientPool && client) { - if (poolBorrowGuard.length) { - activeConnections++; - logConnectionUsage && adapter.log.debug(`Borrow returned connection directly: ${activeConnections} now`); - const callback = poolBorrowGuard.shift(); - callback(null, client); - } else { - try { - clientPool.return(client); - } catch (err) { - // Ignore - } - } - } -} - -function reInit(id, realId, formerAliasId, obj) { - - // maxLength - if (!obj.common.custom[adapter.namespace].maxLength && obj.common.custom[adapter.namespace].maxLength !== '0' && obj.common.custom[adapter.namespace].maxLength !== 0) { - obj.common.custom[adapter.namespace].maxLength = parseInt(adapter.config.maxLength, 10) || 0; - } else { - obj.common.custom[adapter.namespace].maxLength = parseInt(obj.common.custom[adapter.namespace].maxLength, 10); - } - - // retention - if (obj.common.custom[adapter.namespace].retention || obj.common.custom[adapter.namespace].retention === 0) { - obj.common.custom[adapter.namespace].retention = parseInt(obj.common.custom[adapter.namespace].retention, 10) || 0; - } else { - obj.common.custom[adapter.namespace].retention = adapter.config.retention; - } - if (obj.common.custom[adapter.namespace].retention === -1) { - // customRetentionDuration - if (obj.common.custom[adapter.namespace].customRetentionDuration !== undefined && obj.common.custom[adapter.namespace].customRetentionDuration !== null && obj.common.custom[adapter.namespace].customRetentionDuration !== '') { - obj.common.custom[adapter.namespace].customRetentionDuration = parseInt(obj.common.custom[adapter.namespace].customRetentionDuration, 10) || 0; - } else { - obj.common.custom[adapter.namespace].customRetentionDuration = adapter.config.customRetentionDuration; - } - obj.common.custom[adapter.namespace].retention = obj.common.custom[adapter.namespace].customRetentionDuration * 24 * 60 * 60 - } - - // debounceTime and debounce compatibility handling - if (!obj.common.custom[adapter.namespace].blockTime && obj.common.custom[adapter.namespace].blockTime !== '0' && obj.common.custom[adapter.namespace].blockTime !== 0) { - if (!obj.common.custom[adapter.namespace].debounce && obj.common.custom[adapter.namespace].debounce !== '0' && obj.common.custom[adapter.namespace].debounce !== 0) { - obj.common.custom[adapter.namespace].blockTime = parseInt(adapter.config.blockTime, 10) || 0; - } else { - obj.common.custom[adapter.namespace].blockTime = parseInt(obj.common.custom[adapter.namespace].debounce, 10) || 0; - } - } else { - obj.common.custom[adapter.namespace].blockTime = parseInt(obj.common.custom[adapter.namespace].blockTime, 10) || 0; - } - if (!obj.common.custom[adapter.namespace].debounceTime && obj.common.custom[adapter.namespace].debounceTime !== '0' && obj.common.custom[adapter.namespace].debounceTime !== 0) { - obj.common.custom[adapter.namespace].debounceTime = parseInt(adapter.config.debounceTime, 10) || 0; - } else { - obj.common.custom[adapter.namespace].debounceTime = parseInt(obj.common.custom[adapter.namespace].debounceTime, 10) || 0; - } - - // changesOnly - obj.common.custom[adapter.namespace].changesOnly = obj.common.custom[adapter.namespace].changesOnly === 'true' || obj.common.custom[adapter.namespace].changesOnly === true; - - // ignoreZero - obj.common.custom[adapter.namespace].ignoreZero = obj.common.custom[adapter.namespace].ignoreZero === 'true' || obj.common.custom[adapter.namespace].ignoreZero === true; - - // round - if (obj.common.custom[adapter.namespace].round !== null && obj.common.custom[adapter.namespace].round !== undefined && obj.common.custom[adapter.namespace] !== '') { - obj.common.custom[adapter.namespace].round = parseInt(obj.common.custom[adapter.namespace], 10); - if (!isFinite(obj.common.custom[adapter.namespace].round) || obj.common.custom[adapter.namespace].round < 0) { - obj.common.custom[adapter.namespace].round = adapter.config.round; - } else { - obj.common.custom[adapter.namespace].round = Math.pow(10, parseInt(obj.common.custom[adapter.namespace].round, 10)); - } - } else { - obj.common.custom[adapter.namespace].round = adapter.config.round; - } - - // ignoreAboveNumber - if (obj.common.custom[adapter.namespace].ignoreAboveNumber !== undefined && obj.common.custom[adapter.namespace].ignoreAboveNumber !== null && obj.common.custom[adapter.namespace].ignoreAboveNumber !== '') { - obj.common.custom[adapter.namespace].ignoreAboveNumber = parseFloat(obj.common.custom[adapter.namespace].ignoreAboveNumber) || null; - } - - // ignoreBelowNumber incl. ignoreBelowZero compatibility handling - if (obj.common.custom[adapter.namespace].ignoreBelowNumber !== undefined && obj.common.custom[adapter.namespace].ignoreBelowNumber !== null && obj.common.custom[adapter.namespace].ignoreBelowNumber !== '') { - obj.common.custom[adapter.namespace].ignoreBelowNumber = parseFloat(obj.common.custom[adapter.namespace].ignoreBelowNumber) || null; - } else if (obj.common.custom[adapter.namespace].ignoreBelowZero === 'true' || obj.common.custom[adapter.namespace].ignoreBelowZero === true) { - obj.common.custom[adapter.namespace].ignoreBelowNumber = 0; - } - - // disableSkippedValueLogging - if (obj.common.custom[adapter.namespace].disableSkippedValueLogging !== undefined && obj.common.custom[adapter.namespace].disableSkippedValueLogging !== null && obj.common.custom[adapter.namespace].disableSkippedValueLogging !== '') { - obj.common.custom[adapter.namespace].disableSkippedValueLogging = obj.common.custom[adapter.namespace].disableSkippedValueLogging === 'true' || obj.common.custom[adapter.namespace].disableSkippedValueLogging === true; - } else { - obj.common.custom[adapter.namespace].disableSkippedValueLogging = adapter.config.disableSkippedValueLogging; - } - - // enableDebugLogs - if (obj.common.custom[adapter.namespace].enableDebugLogs !== undefined && obj.common.custom[adapter.namespace].enableDebugLogs !== null && obj.common.custom[adapter.namespace].enableDebugLogs !== '') { - obj.common.custom[adapter.namespace].enableDebugLogs = obj.common.custom[adapter.namespace].enableDebugLogs === 'true' || obj.common.custom[adapter.namespace].enableDebugLogs === true; - } else { - obj.common.custom[adapter.namespace].enableDebugLogs = adapter.config.enableDebugLogs; - } - - // changesRelogInterval - if (obj.common.custom[adapter.namespace].changesRelogInterval || obj.common.custom[adapter.namespace].changesRelogInterval === 0) { - obj.common.custom[adapter.namespace].changesRelogInterval = parseInt(obj.common.custom[adapter.namespace].changesRelogInterval, 10) || 0; - } else { - obj.common.custom[adapter.namespace].changesRelogInterval = adapter.config.changesRelogInterval; - } - - // changesMinDelta - if (obj.common.custom[adapter.namespace].changesMinDelta || obj.common.custom[adapter.namespace].changesMinDelta === 0) { - obj.common.custom[adapter.namespace].changesMinDelta = parseFloat(obj.common.custom[adapter.namespace].changesMinDelta.toString().replace(/,/g, '.')) || 0; - } else { - obj.common.custom[adapter.namespace].changesMinDelta = adapter.config.changesMinDelta; - } - - // storageType - if (!obj.common.custom[adapter.namespace].storageType) { - obj.common.custom[adapter.namespace].storageType = false; - } - - // add one day if retention is too small - if (obj.common.custom[adapter.namespace].retention && obj.common.custom[adapter.namespace].retention <= 604800) { - obj.common.custom[adapter.namespace].retention += 86400; - } - - if (sqlDPs[formerAliasId] && - sqlDPs[formerAliasId][adapter.namespace] && - isEqual(obj.common.custom[adapter.namespace], sqlDPs[formerAliasId][adapter.namespace])) { - return obj.common.custom[adapter.namespace].enableDebugLogs && adapter.log.debug(`Object ${id} unchanged. Ignore`); - } - - // relogTimeout - if (sqlDPs[formerAliasId] && sqlDPs[formerAliasId].relogTimeout) { - clearTimeout(sqlDPs[formerAliasId].relogTimeout); - sqlDPs[formerAliasId].relogTimeout = null; - } - - const writeNull = !(sqlDPs[id] && sqlDPs[id][adapter.namespace]); - const state = sqlDPs[id] ? sqlDPs[id].state : null; - const list = sqlDPs[id] ? sqlDPs[id].list : null; - const inFlight = sqlDPs[id] ? sqlDPs[id].inFlight: null; - const timeout = sqlDPs[id] ? sqlDPs[id].timeout : null; - const ts = sqlDPs[id] ? sqlDPs[id].ts : null; - const lastCheck = sqlDPs[id] ? sqlDPs[id].lastCheck : null; - - sqlDPs[id] = obj.common.custom; - sqlDPs[id].state = state; - sqlDPs[id].list = list || []; - sqlDPs[id].inFlight = inFlight || {}; - sqlDPs[id].timeout = timeout; - sqlDPs[id].ts = ts; - sqlDPs[id].realId = realId; - sqlDPs[id].lastCheck = lastCheck || Date.now() - Math.floor(Math.random() * 21600000/* 6 hours */); // randomize lastCheck to avoid all datapoints to be checked at same timepoint - ; - - // changesRelogInterval - if (sqlDPs[id][adapter.namespace].changesOnly && sqlDPs[id][adapter.namespace].changesRelogInterval > 0) { - sqlDPs[id].relogTimeout = setTimeout(reLogHelper, (sqlDPs[id][adapter.namespace].changesRelogInterval * 500 * Math.random()) + sqlDPs[id][adapter.namespace].changesRelogInterval * 500, id); - } - - if (writeNull && adapter.config.writeNulls) { - writeNulls(id); - } - - adapter.log.info(`enabled logging of ${id}, Alias=${id !== realId}, WriteNulls=${writeNull}`); -} - -function setConnected(isConnected) { - if (connected !== isConnected) { - connected = isConnected; - adapter.setState('info.connection', connected, true); - } -} - -let postgresDbCreated = false; -function connect(callback) { - if (reconnectTimeout) { - clearTimeout(reconnectTimeout); - reconnectTimeout = null; - } - - if (!clientPool) { - setConnected(false); - - let params = { - server: adapter.config.host, // needed for MSSQL - host: adapter.config.host, // needed for PostgreSQL , MySQL - user: adapter.config.user || '', - password: adapter.config.password || '', - max_idle: (adapter.config.dbtype === 'sqlite') ? 1 : 2 - }; - - if (adapter.config.maxConnections) { - params.max_active = adapter.config.maxConnections + 1; // we use our own Pool limiter logic, so let library have one more to not block us too early - params.max_wait = 10000; // hard code for now - params.when_exhausted = 'block'; - } - - if (adapter.config.port) { - params.port = adapter.config.port; - } - - if (!adapter.config.dbtype) { - return adapter.log.error('DB Type is not defined!'); - } else - if (!clients[adapter.config.dbtype] || !clients[adapter.config.dbtype].name) { - return adapter.log.error(`Unknown type "${adapter.config.dbtype}"`); - } else - if (!SQL[clients[adapter.config.dbtype].name]) { - return adapter.log.error(`SQL package "${clients[adapter.config.dbtype].name}" is not installed.`); - } - - if (adapter.config.dbtype === 'postgresql') { - params.database = 'postgres'; - if (adapter.config.encrypt) { - params.ssl = { - rejectUnauthorized: !!adapter.config.rejectUnauthorized - }; - } - } else if (adapter.config.dbtype === 'mssql') { - params.options = { - encrypt: !!adapter.config.encrypt, - }; - if (adapter.config.encrypt) { - params.options.trustServerCertificate = !adapter.config.rejectUnauthorized - } - } else if (adapter.config.dbtype === 'mysql') { - if (adapter.config.encrypt) { - params.ssl = { - rejectUnauthorized: !!adapter.config.rejectUnauthorized - }; - } - } - - if (adapter.config.dbtype === 'sqlite') { - params = getSqlLiteDir(adapter.config.fileName); - } else - // special solution for postgres. Connect first to Db "postgres", create new DB "iobroker" and then connect to "iobroker" DB. - if (adapter.config.dbtype === 'postgresql' && !postgresDbCreated) { - // connect first to DB postgres and create iobroker DB - adapter.log.info(`Postgres connection options: ${JSON.stringify(params).replace(params.password, '****')}`); - const _client = new SQL[clients[adapter.config.dbtype].name](params); - _client.on && _client.on('error', err => - adapter.log.warn(`SQL client error: ${err}`)); - - return _client.connect(err => { - if (err) { - adapter.log.error(err); - reconnectTimeout && clearTimeout(reconnectTimeout); - reconnectTimeout = setTimeout(() => connect(callback), 30000); - return; - } - - if (adapter.config.doNotCreateDatabase) { - _client.disconnect(); - postgresDbCreated = true; - reconnectTimeout && clearTimeout(reconnectTimeout); - reconnectTimeout = setTimeout(() => connect(callback), 100); - } else { - _client.execute(`CREATE DATABASE ${adapter.config.dbname};`, (err /* , rows, fields */) => { - _client.disconnect(); - if (err && err.code !== '42P04') { // if error not about yet exists - postgresDbCreated = false; - adapter.log.error(err); - reconnectTimeout && clearTimeout(reconnectTimeout); - reconnectTimeout = setTimeout(() => connect(callback), 30000); - } else { - postgresDbCreated = true; - reconnectTimeout && clearTimeout(reconnectTimeout); - reconnectTimeout = setTimeout(() => connect(callback), 100); - } - }); - } - }); - } - - if (adapter.config.dbtype === 'postgresql') { - params.database = adapter.config.dbname; - } - - try { - if (!clients[adapter.config.dbtype].name) { - adapter.log.error(`Unknown SQL type selected: "${adapter.config.dbtype}"`); - } else if (!SQL[`${clients[adapter.config.dbtype].name}Pool`]) { - adapter.log.error(`Selected SQL DB was not installed properly: "${adapter.config.dbtype}". SQLite requires build tools on system. See README.md`); - } else { - clientPool = new SQL[`${clients[adapter.config.dbtype].name}Pool`](params); - - return clientPool.open(err => { - activeConnections = 0; - if (err) { - clientPool = null; - setConnected(false); - adapter.log.error(err); - reconnectTimeout && clearTimeout(reconnectTimeout); - reconnectTimeout = setTimeout(() => connect(callback), 30000); - } else { - reconnectTimeout && clearTimeout(reconnectTimeout); - reconnectTimeout = setImmediate(() => connect(callback)); - } - }); - } - } catch (ex) { - if (ex.toString() === 'TypeError: undefined is not a function') { - adapter.log.error(`Node.js DB driver for "${adapter.config.dbtype}" could not be installed.`); - } else { - adapter.log.error(ex.toString()); - adapter.log.error(ex.stack); - } - clientPool = null; - activeConnections = 0; - setConnected(false); - reconnectTimeout && clearTimeout(reconnectTimeout); - reconnectTimeout = setTimeout(() => connect(callback), 30000); - return; - } - } - - allScripts(SQLFuncs.init(adapter.config.dbname, adapter.config.doNotCreateDatabase), err => { - if (err) { - //adapter.log.error(err); - reconnectTimeout && clearTimeout(reconnectTimeout); - reconnectTimeout = setTimeout(() => connect(callback), 30000); - } else { - adapter.log.info(`Connected to ${adapter.config.dbtype}`); - // read all DB IDs and all FROM ids - getAllIds(() => - getAllFroms(callback)); - } - }); -} - -// Find sqlite data directory -function getSqlLiteDir(fileName) { - fileName = fileName || 'sqlite.db'; - fileName = fileName.replace(/\\/g, '/'); - if (fileName[0] === '/' || fileName.match(/^\w:\//)) { - return fileName; - } else { - // normally /opt/iobroker/node_modules/iobroker.js-controller - // but can be /example/ioBroker.js-controller - let config = path.join(utils.getAbsoluteDefaultDataDir(), 'sqlite'); - - // create sqlite directory - if (!fs.existsSync(config)) { - fs.mkdirSync(config); - } - - return path.normalize(path.join(config, fileName)); - } -} - -function testConnection(msg) { - if (!msg.message.config) { - return msg.callback && adapter.sendTo(msg.from, msg.command, {error: 'invalid config'}, msg.callback); - } - - msg.message.config.port = parseInt(msg.message.config.port, 10) || 0; - let params = { - server: msg.message.config.host, - host: msg.message.config.host, - user: msg.message.config.user, - password: msg.message.config.password - }; - - if (msg.message.config.port) { - params.port = msg.message.config.port; - } - - if (msg.message.config.dbtype === 'postgresql' && !SQL.PostgreSQLClient) { - const postgres = require('./lib/postgresql-client'); - Object.keys(postgres) - .filter(attr => !SQL[attr]) - .forEach(attr => SQL[attr] = postgres[attr]); - } else if (msg.message.config.dbtype === 'mssql' && !SQL.MSSQLClient) { - const mssql = require('./lib/mssql-client'); - Object.keys(mssql) - .filter(attr => !SQL[attr]) - .forEach(attr => SQL[attr] = mssql[attr]); - } else if (msg.message.config.dbtype === 'mysql' && !SQL.MySQL2Client) { - const mysql = require('./lib/mysql-client'); - Object.keys(mysql) - .filter(attr => !SQL[attr]) - .forEach(attr => SQL[attr] = mysql[attr]); - } - - if (msg.message.config.dbtype === 'postgresql') { - params.database = 'postgres'; - } else if (msg.message.config.dbtype === 'sqlite') { - params = getSqlLiteDir(msg.message.config.fileName); - } - - if (msg.message.config.dbtype === 'postgresql') { - if (msg.message.config.encrypt) { - params.ssl = { - rejectUnauthorized: !!msg.message.config.rejectUnauthorized - }; - } - } else if (msg.message.config.dbtype === 'mssql') { - params.options = { - encrypt: !!msg.message.config.encrypt, - }; - if (msg.message.config.encrypt) { - params.options. trustServerCertificate= !msg.message.config.rejectUnauthorized - } - } else if (msg.message.config.dbtype === 'mysql') { - if (msg.message.config.encrypt) { - params.ssl = { - rejectUnauthorized: !!msg.message.config.rejectUnauthorized - }; - } - } - - try { - const client = new SQL[clients[msg.message.config.dbtype].name](params); - client.on && client.on('error', err => - adapter.log.warn(`SQL client error: ${err}`)); - - testConnectTimeout = setTimeout(() => { - testConnectTimeout = null; - adapter.sendTo(msg.from, msg.command, {error: 'connect timeout'}, msg.callback); - }, 5000); - - client.connect(err => { - if (err) { - if (testConnectTimeout) { - clearTimeout(testConnectTimeout); - testConnectTimeout = null; - } - return adapter.sendTo(msg.from, msg.command, {error: err.toString()}, msg.callback); - } - - client.execute('SELECT 2 + 3 AS x', (err /* , rows, fields */) => { - client.disconnect(); - - if (testConnectTimeout) { - clearTimeout(testConnectTimeout); - testConnectTimeout = null; - return adapter.sendTo(msg.from, msg.command, {error: err ? err.toString() : null}, msg.callback); - } - }); - }); - } catch (ex) { - if (testConnectTimeout) { - clearTimeout(testConnectTimeout); - testConnectTimeout = null; - } - if (ex.toString() === 'TypeError: undefined is not a function') { - return adapter.sendTo(msg.from, msg.command, {error: 'Node.js DB driver could not be installed.'}, msg.callback); - } else { - return adapter.sendTo(msg.from, msg.command, {error: ex.toString()}, msg.callback); - } - } -} - -function destroyDB(msg) { - try { - allScripts(SQLFuncs.destroy(adapter.config.dbname), err => { - if (err) { - adapter.log.error(err); - adapter.sendTo(msg.from, msg.command, {error: err.toString()}, msg.callback); - } else { - adapter.sendTo(msg.from, msg.command, {error: null, result: 'deleted'}, msg.callback); - // restart adapter - setTimeout(() => - adapter.getForeignObject(`system.adapter.${adapter.namespace}`, (err, obj) => { - if (!err) { - adapter.setForeignObject(obj._id, obj); - } else { - adapter.log.error(`Cannot read object "system.adapter.${adapter.namespace}": ${err}`); - adapter.stop(); - } - }), 2000); - } - }); - } catch (ex) { - return adapter.sendTo(msg.from, msg.command, {error: ex.toString()}, msg.callback); - } -} - -function _userQuery(msg, callback) { - try { - if (typeof msg.message !== 'string' || !msg.message.length) { - throw new Error('No query provided'); - } - adapter.log.debug(msg.message); - - borrowClientFromPool((err, client) => { - if (err) { - adapter.sendTo(msg.from, msg.command, {error: err.toString()}, msg.callback); - returnClientToPool(client); - callback && callback(); - } else { - client.execute(msg.message, (err, rows /* , fields */) => { - if (rows && rows.rows) rows = rows.rows; - returnClientToPool(client); - //convert ts for postgresql and ms sqlserver - if (!err && rows && rows[0] && typeof rows[0].ts === 'string') { - for (let i = 0; i < rows.length; i++) { - rows[i].ts = parseInt(rows[i].ts, 10); - } - } - adapter.sendTo(msg.from, msg.command, {error: err ? err.toString() : null, result: rows}, msg.callback); - callback && callback(); - }); - } - }); - } catch (err) { - adapter.sendTo(msg.from, msg.command, {error: err.toString()}, msg.callback); - callback && callback(); - } -} - -// execute custom query -function query(msg) { - if (!multiRequests) { - if (tasks.length > MAXTASKS) { - const error = `Cannot queue new requests, because more than ${MAXTASKS}`; - adapter.log.error(error); - adapter.sendTo(msg.from, msg.command, {error}, msg.callback); - } else { - tasks.push({operation: 'userQuery', msg}); - tasks.length === 1 && processTasks(); - } - } else { - _userQuery(msg); - } -} - -// one script -function oneScript(script, cb) { - try { - borrowClientFromPool((err, client) => { - if (err || !client) { - clientPool && clientPool.close(); - activeConnections = 0; - clientPool = null; - setConnected(false); - adapter.log.error(err); - return cb && cb(err); - } - - adapter.log.debug(script); - - client.execute(script, (err /* , rows, fields */) => { - adapter.log.debug(`Response: ${JSON.stringify(err)}`); - if (err) { - // Database 'iobroker' already exists. Choose a different database name. - if (err.number === 1801 || - //There is already an object named 'sources' in the database. - err.number === 2714) { - // do nothing - err = null; - } else - if (err.message && err.message.match(/^SQLITE_ERROR: table [\w_]+ already exists$/)) { - // do nothing - err = null; - } else - if (err.errno == 1007 || err.errno == 1050) { // if database exists or table exists - // do nothing - err = null; - } else - if (err.code === '42P04') {// if database exists or table exists - // do nothing - err = null; - } - else if (err.code === '42P07') { - const match = script.match(/CREATE\s+TABLE\s+(\w*)\s+\(/); - if (match) { - adapter.log.debug(`OK. Table "${match[1]}" yet exists`); - err = null; - } else { - adapter.log.error(script); - adapter.log.error(err); - } - } else if (script.startsWith('CREATE INDEX')) { - adapter.log.info('Ignore Error on Create index. You might want to create the index yourself!'); - adapter.log.info(script); - adapter.log.info(`${err.code}: ${err}`); - err = null; - } else { - adapter.log.error(script); - adapter.log.error(err); - } - } - returnClientToPool(client); - cb && cb(err); - }); - }); - } catch (ex) { - adapter.log.error(ex); - cb && cb(ex); - } - -} - -// all scripts -function allScripts(scripts, index, cb) { - if (typeof index === 'function') { - cb = index; - index = 0; - } - index = index || 0; - - if (scripts && index < scripts.length) { - oneScript(scripts[index], err => { - if (err) { - cb && cb(err); - } else { - allScripts(scripts, index + 1, cb); - } - }); - } else { - cb && cb(); - } -} - -function finish(callback) { - - function allFinished() { - if (clientPool) { - clientPool.close(); - activeConnections = 0; - clientPool = null; - setConnected(false); - } - if (typeof finished === 'object') { - setTimeout(cb => { - for (let f = 0; f < cb.length; f++) { - typeof cb[f] === 'function' && cb[f](); - } - }, 500, finished); - finished = true; - } - } - - function finishId(id) { - if (!sqlDPs[id]) { - return; - } - if (sqlDPs[id].relogTimeout) { - clearTimeout(sqlDPs[id].relogTimeout); - sqlDPs[id].relogTimeout = null; - } - if (sqlDPs[id].timeout) { - clearTimeout(sqlDPs[id].timeout); - sqlDPs[id].timeout = null; - } - let tmpState = Object.assign({}, sqlDPs[id].state); - const state = sqlDPs[id].state ? tmpState : null; - - if (sqlDPs[id].skipped && !(sqlDPs[id][adapter.namespace] && sqlDPs[id][adapter.namespace].disableSkippedValueLogging)) { - count++; - pushValueIntoDB(id, sqlDPs[id].skipped, false, true,() => { - if (!--count) { - pushValuesIntoDB(id, sqlDPs[id].list, () => { - allFinished(); - }); - } - }); - sqlDPs[id].skipped = null; - } - - const nullValue = {val: null, ts: now, lc: now, q: 0x40, from: `system.adapter.${adapter.namespace}`}; - - if (sqlDPs[id][adapter.namespace] && adapter.config.writeNulls) { - if (sqlDPs[id][adapter.namespace].changesOnly && state && state.val !== null) { - count++; - (function (_id, _state, _nullValue) { - _state.ts = now; - _state.from = `system.adapter.${adapter.namespace}`; - nullValue.ts += 4; - nullValue.lc += 4; // because of MS SQL - adapter.log.debug(`Write 1/2 "${_state.val}" _id: ${_id}`); - pushValueIntoDB(_id, _state, false, true,() => { - // terminate values with null to indicate adapter stop. timestamp + 1 - adapter.log.debug(`Write 2/2 "null" _id: ${_id}`); - pushValueIntoDB(_id, _nullValue, false, true,() => { - if (!--count) { - pushValuesIntoDB(id, sqlDPs[id].list, () => { - allFinished(); - }); - } - }); - }); - })(id, state, nullValue); - } else { - // terminate values with null to indicate adapter stop. timestamp + 1 - count++; - adapter.log.debug(`Write 0 NULL _id: ${id}`); - pushValueIntoDB(id, nullValue, false, true,() => { - if (!--count) { - pushValuesIntoDB(id, sqlDPs[id].list, () => { - allFinished(); - }); - } - }); - } - } - } - - if (!subscribeAll) { - for (const _id in sqlDPs) { - if (sqlDPs.hasOwnProperty(_id) && sqlDPs.hasOwnProperty(sqlDPs[_id].realId)) { - adapter.unsubscribeForeignStates(sqlDPs[_id].realId); - } - } - } else { - subscribeAll = false; - adapter.unsubscribeForeignStates('*'); - } - - if (reconnectTimeout) { - clearTimeout(reconnectTimeout); - reconnectTimeout = null; - } - if (testConnectTimeout) { - clearTimeout(testConnectTimeout); - testConnectTimeout = null; - } - if (dpOverviewTimeout) { - clearTimeout(dpOverviewTimeout); - dpOverviewTimeout = null; - } - if (bufferChecker) { - clearInterval(bufferChecker); - bufferChecker = null; - } - - let count = 0; - if (finished) { - if (callback) { - if (finished === true) { - callback(); - } else { - finished.push(callback); - } - } - return; - } - finished = [callback]; - const now = Date.now(); - let dpcount = 0; - let delay = 0; - for (const id in sqlDPs) { - if (!sqlDPs.hasOwnProperty(id)) { - continue; - } - dpcount++; - delay += (dpcount%50 === 0) ? 1000: 0; - setTimeout(finishId, delay, id); - } - - if (!dpcount && callback) { - if (clientPool) { - clientPool.close(); - activeConnections = 0; - clientPool = null; - setConnected(false); - } - callback(); - } -} - -function processMessage(msg) { - if (msg.command === 'features') { - adapter.sendTo(msg.from, msg.command, {supportedFeatures: ['update', 'delete', 'deleteRange', 'deleteAll', 'storeState']}, msg.callback); - } else - if (msg.command === 'getHistory') { - getHistory(msg); - } else if (msg.command === 'getCounter') { - getCounterDiff(msg); - } else if (msg.command === 'test') { - testConnection(msg); - } else if (msg.command === 'destroy') { - destroyDB(msg); - } else if (msg.command === 'query') { - query(msg); - } else if (msg.command === 'update') { - updateState(msg); - } else if (msg.command === 'delete') { - deleteState(msg); - } else if (msg.command === 'deleteAll') { - deleteStateAll(msg); - } else if (msg.command === 'deleteRange') { - deleteState(msg); - } else if (msg.command === 'storeState') { - storeState(msg); - } else if (msg.command === 'getDpOverview') { - getDpOverview(msg); - } else if (msg.command === 'enableHistory') { - enableHistory(msg); - } else if (msg.command === 'disableHistory') { - disableHistory(msg); - } else if (msg.command === 'getEnabledDPs') { - getEnabledDPs(msg); - } else if (msg.command === 'stopInstance') { - finish(() => { - if (msg.callback) { - adapter.sendTo(msg.from, msg.command, 'stopped', msg.callback); - setTimeout(() => adapter.stop(), 200); - } - }); - } -} - -function processStartValues(callback) { - if (tasksStart && tasksStart.length) { - const task = tasksStart.shift(); - if (sqlDPs[task.id][adapter.namespace].changesOnly) { - adapter.getForeignState(sqlDPs[task.id].realId, (err, state) => { - const now = task.now || Date.now(); - pushHistory(task.id, { - val: null, - ts: state ? now - 4 : now, // 4 is because of MS SQL - lc: state ? now - 4 : now, // 4 is because of MS SQL - ack: true, - q: 0x40, - from: `system.adapter.${adapter.namespace}`, - }); - - if (state) { - state.ts = now; - state.lc = now; - state.from = `system.adapter.${adapter.namespace}`; - pushHistory(task.id, state); - } - - setImmediate(processStartValues); - }); - } - else { - const now = Date.now(); - pushHistory(task.id, { - val: null, - ts: task.now || now, - lc: task.now || now, - ack: true, - q: 0x40, - from: `system.adapter.${adapter.namespace}` - }); - - setImmediate(processStartValues); - } - if (sqlDPs[task.id][adapter.namespace] && sqlDPs[task.id][adapter.namespace].changesOnly && sqlDPs[task.id][adapter.namespace].changesRelogInterval > 0) { - sqlDPs[task.id].relogTimeout && clearTimeout(sqlDPs[task.id].relogTimeout); - sqlDPs[task.id].relogTimeout = setTimeout(reLogHelper, (sqlDPs[task.id][adapter.namespace].changesRelogInterval * 500 * Math.random()) + sqlDPs[task.id][adapter.namespace].changesRelogInterval * 500, task.id); - } - } else { - callback && callback(); - } -} - -function writeNulls(id, now) { - if (!id) { - now = Date.now(); - - Object.keys(sqlDPs) - .filter(_id => sqlDPs[_id] && sqlDPs[_id][adapter.namespace]) - .forEach(_id => writeNulls(_id, now)); - } else { - now = now || Date.now(); - - tasksStart.push({id, now}); - - if (tasksStart.length === 1 && connected) { - processStartValues(); - } - } -} - -function pushHistory(id, state, timerRelog) { - if (timerRelog === undefined) { - timerRelog = false; - } - - // Push into DB - if (sqlDPs[id]) { - const settings = sqlDPs[id][adapter.namespace]; - - if (!settings || !state) { - return; - } - - if (state && state.val === undefined) { - return adapter.log.warn(`state value undefined received for ${id} which is not allowed. Ignoring.`); - } - - if (typeof state.val === 'string' && settings.storageType !== 'String') { - if (isFinite(state.val)) { - state.val = parseFloat(state.val); - } - } - - settings.enableDebugLogs && adapter.log.debug(`new value received for ${id} (storageType ${settings.storageType}), new-value=${state.val}, ts=${state.ts}, relog=${timerRelog}`); - - let ignoreDebonce = false; - - if (!timerRelog) { - const valueUnstable = !!sqlDPs[id].timeout; - // When a debounce timer runs and the value is the same as the last one, ignore it - if (sqlDPs[id].timeout && state.ts !== state.lc) { - settings.enableDebugLogs && adapter.log.debug(`value not changed debounce ${id}, value=${state.val}, ts=${state.ts}, debounce timer keeps running`); - return; - } else if (sqlDPs[id].timeout) { // if value changed, clear timer - settings.enableDebugLogs && adapter.log.debug(`value changed during debounce time ${id}, value=${state.val}, ts=${state.ts}, debounce timer restarted`); - clearTimeout(sqlDPs[id].timeout); - sqlDPs[id].timeout = null; - } - - if (!valueUnstable && settings.blockTime && sqlDPs[id].state && (sqlDPs[id].state.ts + settings.blockTime) > state.ts) { - settings.enableDebugLogs && adapter.log.debug(`value ignored blockTime ${id}, value=${state.val}, ts=${state.ts}, lastState.ts=${sqlDPs[id].state.ts}, blockTime=${settings.blockTime}`); - return; - } - - if (settings.ignoreZero && (state.val === undefined || state.val === null || state.val === 0)) { - settings.enableDebugLogs && adapter.log.debug(`value ignore because zero or null ${id}, new-value=${state.val}, ts=${state.ts}`); - return; - } else - if (typeof settings.ignoreBelowNumber === 'number' && typeof state.val === 'number' && state.val < settings.ignoreBelowNumber) { - settings.enableDebugLogs && adapter.log.debug(`value ignored because below ${settings.ignoreBelowNumber} for ${id}, new-value=${state.val}, ts=${state.ts}`); - return; - } - if (typeof settings.ignoreAboveNumber === 'number' && typeof state.val === 'number' && state.val > settings.ignoreAboveNumber) { - settings.enableDebugLogs && adapter.log.debug(`value ignored because above ${settings.ignoreAboveNumber} for ${id}, new-value=${state.val}, ts=${state.ts}`); - return; - } - - if (sqlDPs[id].state && settings.changesOnly) { - if (settings.changesRelogInterval === 0) { - if ((sqlDPs[id].state.val !== null || state.val === null) && state.ts !== state.lc) { - // remember new timestamp - if (!valueUnstable && !settings.disableSkippedValueLogging) { - sqlDPs[id].skipped = state; - } - settings.enableDebugLogs && adapter.log.debug(`value not changed ${id}, last-value=${sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); - return; - } - } else if (sqlDPs[id].lastLogTime) { - if ((sqlDPs[id].state.val !== null || state.val === null) && (state.ts !== state.lc) && (Math.abs(sqlDPs[id].lastLogTime - state.ts) < settings.changesRelogInterval * 1000)) { - // remember new timestamp - if (!valueUnstable && !settings.disableSkippedValueLogging) { - sqlDPs[id].skipped = state; - } - settings.enableDebugLogs && adapter.log.debug(`value not changed ${id}, last-value=${sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); - return; - } - if (state.ts !== state.lc) { - settings.enableDebugLogs && adapter.log.debug(`value-not-changed-relog ${id}, value=${state.val}, lastLogTime=${sqlDPs[id].lastLogTime}, ts=${state.ts}`); - ignoreDebonce = true; - } - } - if (typeof state.val === 'number') { - if ( - sqlDPs[id].state.val !== null && - settings.changesMinDelta !== 0 && - Math.abs(sqlDPs[id].state.val - state.val) < settings.changesMinDelta - ) { - if (!valueUnstable && !settings.disableSkippedValueLogging) { - sqlDPs[id].skipped = state; - } - settings.enableDebugLogs && adapter.log.debug(`Min-Delta not reached ${id}, last-value=${sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); - return; - } else if (settings.changesMinDelta !== 0) { - settings.enableDebugLogs && adapter.log.debug(`Min-Delta reached ${id}, last-value=${sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); - } - } else { - settings.enableDebugLogs && adapter.log.debug(`Min-Delta ignored because no number ${id}, last-value=${sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`); - } - } - } - - if (settings.counter && sqlDPs[id].state) { - if (sqlDPs[id].type !== types.number) { - adapter.log.error('Counter must have type "number"!'); - } else if (state.val < sqlDPs[id].state.val) { - // if the actual value is less then last seen counter, store both values - pushValueIntoDB(id, sqlDPs[id].state, true); - pushValueIntoDB(id, state, true); - } - } - - if (sqlDPs[id].relogTimeout) { - clearTimeout(sqlDPs[id].relogTimeout); - sqlDPs[id].relogTimeout = null; - } - - if (timerRelog) { - state = Object.assign({}, state); - state.ts = Date.now(); - state.from = `system.adapter.${adapter.namespace}`; - settings.enableDebugLogs && adapter.log.debug(`timed-relog ${id}, value=${state.val}, lastLogTime=${sqlDPs[id].lastLogTime}, ts=${state.ts}`); - ignoreDebonce = true; - } else { - if (settings.changesOnly && sqlDPs[id].skipped) { - settings.enableDebugLogs && adapter.log.debug(`Skipped value logged ${id}, value=${sqlDPs[id].skipped.val}, ts=${sqlDPs[id].skipped.ts}`); - pushHelper(id, sqlDPs[id].skipped); - sqlDPs[id].skipped = null; - } - if (sqlDPs[id].state && ((sqlDPs[id].state.val === null && state.val !== null) || (sqlDPs[id].state.val !== null && state.val === null))) { - ignoreDebonce = true; - } else if (!sqlDPs[id].state && state.val === null) { - ignoreDebonce = true; - } - } - - if (settings.debounceTime && !ignoreDebonce && !timerRelog) { - // Discard changes in the debounce time to store last stable value - sqlDPs[id].timeout && clearTimeout(sqlDPs[id].timeout); - sqlDPs[id].timeout = setTimeout((id, state) => { - if (!sqlDPs[id]) return; - sqlDPs[id].timeout = null; - sqlDPs[id].state = state; - sqlDPs[id].lastLogTime = state.ts; - settings.enableDebugLogs && adapter.log.debug(`Value logged ${id}, value=${sqlDPs[id].state.val}, ts=${sqlDPs[id].state.ts}`); - pushHelper(id); - if (settings.changesOnly && settings.changesRelogInterval > 0) { - sqlDPs[id].relogTimeout = setTimeout(reLogHelper, settings.changesRelogInterval * 1000, id); - } - }, settings.debounceTime, id, state); - } else { - if (!timerRelog) { - sqlDPs[id].state = state; - } - sqlDPs[id].lastLogTime = state.ts; - - settings.enableDebugLogs && adapter.log.debug(`Value logged ${id}, value=${sqlDPs[id].state.val}, ts=${sqlDPs[id].state.ts}`); - pushHelper(id, state); - if (settings.changesOnly && settings.changesRelogInterval > 0) { - sqlDPs[id].relogTimeout = setTimeout(reLogHelper, settings.changesRelogInterval * 1000, id); - } - } - } -} - -function reLogHelper(_id) { - if (!sqlDPs[_id]) { - adapter.log.info(`non-existing id ${_id}`); - } else { - sqlDPs[_id].relogTimeout = null; - if (sqlDPs[_id].skipped) { - pushHistory(_id, sqlDPs[_id].skipped, true); - } else if (sqlDPs[_id].state) { - pushHistory(_id, sqlDPs[_id].state, true); - } else { - adapter.getForeignState(sqlDPs[_id].realId, (err, state) => { - if (err) { - adapter.log.info(`init timed Relog: can not get State for ${_id} : ${err}`); - } else if (!state) { - adapter.log.info(`init timed Relog: disable relog because state not set so far for ${_id}: ${JSON.stringify(state)}`); - } else { - adapter.log.debug(`init timed Relog: getState ${_id}: Value=${state.val}, ack=${state.ack}, ts=${state.ts}, lc=${state.lc}`); - sqlDPs[_id].state = state; - pushHistory(_id, sqlDPs[_id].state, true); - } - }); - } - } -} - -function pushHelper(_id, state, cb) { - if (!sqlDPs[_id] || (!sqlDPs[_id].state && !state)) { - return; - } - if (!state) { - state = sqlDPs[_id].state; - } - - const _settings = sqlDPs[_id] && sqlDPs[_id][adapter.namespace] || {}; - - if (state.val !== null && (typeof state.val === 'object' || typeof state.val === 'undefined')) { - state.val = JSON.stringify(state.val); - } - - if (state.val !== null && state.val !== undefined) { - _settings.enableDebugLogs && adapter.log.debug(`Datatype ${_id}: Currently: ${typeof state.val}, StorageType: ${_settings.storageType}`); - - if (typeof state.val === 'string' && _settings.storageType !== 'String') { - _settings.enableDebugLogs && adapter.log.debug(`Do Automatic Datatype conversion for ${_id}`); - if (isFinite(state.val)) { - state.val = parseFloat(state.val); - } else if (state.val === 'true') { - state.val = true; - } else if (state.val === 'false') { - state.val = false; - } - } - - if (_settings.storageType === 'String' && typeof state.val !== 'string') { - state.val = state.val.toString(); - } else if (_settings.storageType === 'Number' && typeof state.val !== 'number') { - if (typeof state.val === 'boolean') { - state.val = state.val ? 1 : 0; - } else { - return adapter.log.info(`Do not store value "${state.val}" for ${_id} because no number`); - } - } else if (_settings.storageType === 'Boolean' && typeof state.val !== 'boolean') { - state.val = !!state.val; - } - } else { - _settings.enableDebugLogs && adapter.log.debug(`Datatype ${_id}: Currently: null`); - } - - pushValueIntoDB(_id, state, cb); -} - -function getAllIds(cb) { - const query = SQLFuncs.getIdSelect(adapter.config.dbname); - adapter.log.debug(query); - - borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - return cb && cb(err); - } - - client.execute(query, (err, rows /* , fields */) => { - returnClientToPool(client); - if (rows && rows.rows) rows = rows.rows; - if (err) { - adapter.log.error(`Cannot select ${query}: ${err}`); - return cb && cb(err); - } - - if (rows.length) { - let id; - for (let r = 0; r < rows.length; r++) { - id = rows[r].name; - sqlDPs[id] = sqlDPs[id] || {}; - sqlDPs[id].index = rows[r].id; - if (rows[r].type !== null) sqlDPs[id].dbtype = rows[r].type; - } - } - cb && cb(); - }); - }); -} - -function getAllFroms(cb) { - const query = SQLFuncs.getFromSelect(adapter.config.dbname); - adapter.log.debug(query); - - borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - return cb && cb(err); - } - - client.execute(query, (err, rows /* , fields */) => { - returnClientToPool(client); - if (rows && rows.rows) rows = rows.rows; - if (err) { - adapter.log.error(`Cannot select ${query}: ${err}`); - return cb && cb(err); - } - - if (rows.length) { - for (let r = 0; r < rows.length; r++) { - from[rows[r].name] = rows[r].id; - } - } - - cb && cb(); - }); - }); -} - -function _checkRetention(query, cb) { - adapter.log.debug(query); - - borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - adapter.log.error(err); - cb && cb(); - } else { - client.execute(query, (err /* , rows, fields */ ) => { - returnClientToPool(client); - err && adapter.log.warn(`Retention: Cannot delete ${query}: ${err}`); - cb && cb(); - }); - } - }); -} - -function checkRetention(id) { - if (sqlDPs[id] && sqlDPs[id][adapter.namespace] && sqlDPs[id][adapter.namespace].retention) { - const dt = Date.now(); - // check every 6 hours - if (!sqlDPs[id].lastCheck || dt - sqlDPs[id].lastCheck >= 21600000/* 6 hours */) { - sqlDPs[id].lastCheck = dt; - - if (!dbNames[sqlDPs[id].type]) { - adapter.log.error(`No type ${sqlDPs[id].type} found for ${id}. Retention is not possible.`); - } else { - const query = SQLFuncs.retention(adapter.config.dbname, sqlDPs[id].index, dbNames[sqlDPs[id].type], sqlDPs[id][adapter.namespace].retention); - - if (!multiRequests) { - if (tasks.length > MAXTASKS) { - return adapter.log.error(`Cannot queue new requests, because more than ${MAXTASKS}`); - } - - let start = tasks.length === 1; - tasks.push({operation: 'delete', query}); - - // delete counters too - if (sqlDPs[id] && sqlDPs[id].type === 0) { // 0 === number - const query = SQLFuncs.retention(adapter.config.dbname, sqlDPs[id].index, 'ts_counter', sqlDPs[id][adapter.namespace].retention); - tasks.push({operation: 'delete', query}); - } - - start && processTasks(); - } else { - _checkRetention(query, () => { - // delete counters too - if (sqlDPs[id] && sqlDPs[id].type === 0) { // 0 === number - const query = SQLFuncs.retention(adapter.config.dbname, sqlDPs[id].index, 'ts_counter', sqlDPs[id][adapter.namespace].retention); - _checkRetention(query); - } - }); - } - } - } - } -} - -function _insertValueIntoDB(query, id, cb) { - adapter.log.debug(query); - - borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - adapter.log.error(err); - cb && cb(); // BF asked (2021.12.14): may be return here err? - } else { - client.execute(query, (err /* , rows, fields */) => { - returnClientToPool(client); - if (err) { - adapter.log.error(`Cannot insert ${query}: ${err} (id: ${id})`); - } else { - checkRetention(id); - } - cb && cb(); // BF asked (2021.12.14): may be return here err? - }); - } - }); -} - -function _executeQuery(query, id, cb) { - adapter.log.debug(query); - - borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - adapter.log.error(err); - cb && cb(); - } else { - client.execute(query, (err /* , rows, fields */) => { - returnClientToPool(client); - err && adapter.log.error(`Cannot query ${query}: ${err} (id: ${id})`); - cb && cb(); - }); - } - }); -} - -function processReadTypes() { - if (tasksReadType && tasksReadType.length) { - const task = tasksReadType[0]; - - if (!sqlDPs[task.id]) { - adapter.log.warn(`Ignore type lookup for ${task.id} because not enabled anymore`); - task.cb && task.cb(`Ignore type lookup for ${task.id} because not enabled anymore`); - task.cb = null; - return setImmediate(() => { - tasksReadType.shift(); - processReadTypes(); - }); - } - - adapter.log.debug(`Type set in Def for ${task.id}: ${sqlDPs[task.id][adapter.namespace] && sqlDPs[task.id][adapter.namespace].storageType}`); - - if (sqlDPs[task.id][adapter.namespace] && sqlDPs[task.id][adapter.namespace].storageType) { - sqlDPs[task.id].type = types[sqlDPs[task.id][adapter.namespace].storageType.toLowerCase()]; - adapter.log.debug(`Type (from Def) for ${task.id}: ${sqlDPs[task.id].type}`); - processVerifyTypes(task); - } else if (sqlDPs[task.id].dbtype !== undefined) { - sqlDPs[task.id].type = sqlDPs[task.id].dbtype; - if (sqlDPs[task.id][adapter.namespace]) { - sqlDPs[task.id][adapter.namespace].storageType = storageTypes[sqlDPs[task.id].type]; - } - adapter.log.debug(`Type (from DB-Type) for ${task.id}: ${sqlDPs[task.id].type}`); - processVerifyTypes(task); - } else { - adapter.getForeignObject(sqlDPs[task.id].realId, (err, obj) => { - err && adapter.log.warn(`Error while get Object for Def for ${sqlDPs[task.id].realId}: ${err}`); - - if (!sqlDPs[task.id]) { - adapter.log.warn(`Ignore type lookup for ${task.id} because not enabled anymore`); - task.cb && task.cb(`Ignore type lookup for ${task.id} because not enabled anymore`) - task.cb = null; - return setImmediate(() => { - tasksReadType.shift(); - processReadTypes(); - }); - } else - // read type from object - if (obj && obj.common && obj.common.type && types[obj.common.type.toLowerCase()] !== undefined) { - adapter.log.debug(`${obj.common.type.toLowerCase()} / ${types[obj.common.type.toLowerCase()]} / ${JSON.stringify(obj.common)}`); - sqlDPs[task.id].type = types[obj.common.type.toLowerCase()]; - if (sqlDPs[task.id][adapter.namespace]) { - sqlDPs[task.id][adapter.namespace].storageType = storageTypes[sqlDPs[task.id].type]; - } - adapter.log.debug(`Type (from Obj) for ${task.id}: ${sqlDPs[task.id].type}`); - processVerifyTypes(task); - } else if (sqlDPs[task.id].type === undefined) { - adapter.getForeignState(sqlDPs[task.id].realId, (err, state) => { - if (!sqlDPs[task.id]) { - adapter.log.warn(`Ignore type lookup for ${task.id} because not enabled anymore`); - task.cb && task.cb(`Ignore type lookup for ${task.id} because not enabled anymore`) - task.cb = null; - return setImmediate(() => { - tasksReadType.shift(); - processReadTypes(); - }); - } - - if (err && task.state) { - adapter.log.warn(`Fallback to type of current state value because no other valid type found`); - state = task.state; - } - if (state && state.val !== null && state.val !== undefined && types[typeof state.val] !== undefined) { - sqlDPs[task.id].type = types[typeof state.val]; - if (sqlDPs[task.id][adapter.namespace]) { - sqlDPs[task.id][adapter.namespace].storageType = storageTypes[sqlDPs[task.id].type]; - } - } else { - adapter.log.warn(`Store data for ${task.id} as string because no other valid type found (${state ? (typeof state.val) : 'state not existing'})`); - sqlDPs[task.id].type = 1; // string - } - - adapter.log.debug(`Type (from State) for ${task.id}: ${sqlDPs[task.id].type}`); - processVerifyTypes(task); - }); - } else { - // all OK - task.cb && task.cb(); - task.cb = null; - tasksReadType.shift(); - processReadTypes(); - } - }); - } - } -} - -function processVerifyTypes(task) { - if (sqlDPs[task.id].index !== undefined && sqlDPs[task.id].type !== undefined && sqlDPs[task.id].type !== sqlDPs[task.id].dbtype) { - sqlDPs[task.id].dbtype = sqlDPs[task.id].type; - - const query = SQLFuncs.getIdUpdate(adapter.config.dbname, sqlDPs[task.id].index, sqlDPs[task.id].type); - - adapter.log.debug(query); - - return borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - return processVerifyTypes(task); - } - - client.execute(query, (err, rows /* , fields */) => { - returnClientToPool(client); - if (err) { - adapter.log.error(`error updating history config for ${task.id} to pin datatype: ${query}: ${err}`); - } else { - adapter.log.info(`changed history configuration to pin detected datatype for ${task.id}`); - } - processVerifyTypes(task); - }); - }); - } - - task.cb && task.cb(); - task.cb = null; - - setTimeout(() => { - tasksReadType.shift(); - processReadTypes(); - }, 50); -} - -function prepareTaskReadDbId(id, state, isCounter, cb) { - if (!sqlDPs[id]) { - return cb && cb(`${id} not active any more`); - } - const type = sqlDPs[id].type; - - if (type === undefined) { // Can not happen anymore - let warn; - if (state.val === null) { - warn = `Ignore null value for ${id} because no type defined till now.`; - } else { - warn = `Cannot store values of type "${typeof state.val}" for ${id}`; - } - - adapter.log.warn(warn); - return cb && cb(warn); - } - - let tmpState; - // get sql id of state - if (sqlDPs[id].index === undefined) { - sqlDPs[id].isRunning = sqlDPs[id].isRunning || []; - - tmpState = Object.assign({}, state); - - sqlDPs[id].isRunning.push({id, state: tmpState, cb, isCounter}); - - if (sqlDPs[id].isRunning.length === 1) { - // read or create in DB - return getId(id, type, (err, _id) => { - adapter.log.debug(`prepareTaskCheckTypeAndDbId getId Result - isRunning length = ${sqlDPs[_id].isRunning ? sqlDPs[_id].isRunning.length : 'none'}`); - if (err) { - adapter.log.warn(`Cannot get index of "${_id}": ${err}`); - sqlDPs[_id].isRunning && - sqlDPs[_id].isRunning.forEach(r => r.cb && r.cb(`Cannot get index of "${r.id}": ${err}`)); - } else { - sqlDPs[_id].isRunning && - sqlDPs[_id].isRunning.forEach(r => r.cb && r.cb()); - } - - sqlDPs[_id].isRunning = null; - }); - } else { - return; - } - } - - // get from - if (!isCounter && state.from && !from[state.from]) { - isFromRunning[state.from] = isFromRunning[state.from] || []; - tmpState = Object.assign({}, state); - - isFromRunning[state.from].push({id, state: tmpState, cb}); - - if (isFromRunning[state.from].length === 1) { - // read or create in DB - return getFrom(state.from, (err, from) => { - adapter.log.debug(`prepareTaskCheckTypeAndDbId getFrom ${from} Result - isRunning length = ${isFromRunning[from] ? isFromRunning[from].length : 'none'}`) - if (err) { - adapter.log.warn(`Cannot get "from" for "${from}": ${err}`); - isFromRunning[from] && - isFromRunning[from].forEach(f => f.cb && f.cb(`Cannot get "from" for "${from}": ${err}`)); - } else { - isFromRunning[from] && - isFromRunning[from].forEach(f => f.cb && f.cb()); - } - isFromRunning[from] = null; - }); - } else { - return; - } - } - - state.ts = parseInt(state.ts, 10); - - try { - if (state.val !== null && (typeof state.val === 'object' || typeof state.val === 'undefined')) { - state.val = JSON.stringify(state.val); - } - } catch (err) { - const error = `Cannot convert the object value "${id}"`; - adapter.log.error(error); - return cb && cb(error); - } - - cb && cb(); -} - -function prepareTaskCheckTypeAndDbId(id, state, isCounter, cb) { - // check if we know about this ID - if (!sqlDPs[id]) { - return cb && setImmediate(cb, `Unknown ID: ${id}`); - } - - // Check sql connection - if (!clientPool) { - adapter.log.warn('No Connection to database'); - return cb && setImmediate(cb, 'No Connection to database'); - } - adapter.log.debug(`prepareTaskCheckTypeAndDbId CALLED for ${id}`); - - // read type of value - if (sqlDPs[id].type !== undefined) { - prepareTaskReadDbId(id, state, isCounter, cb) - } else { - // read type from DB - tasksReadType.push({id, state, cb: err => { - if (err) { - return cb && cb(err); - } - prepareTaskReadDbId(id, state, isCounter, cb); - }}); - - tasksReadType.length === 1 && processReadTypes(); - } -} - -function pushValueIntoDB(id, state, isCounter, storeInCacheOnly, cb) { - if (typeof storeInCacheOnly === 'function') { - cb = storeInCacheOnly; - storeInCacheOnly = false; - } - - if (typeof isCounter === 'function') { - cb = isCounter; - isCounter = false; - } - - if (!sqlDPs[id] || !state) { - return cb && cb() - } - - adapter.log.debug(`pushValueIntoDB called for ${id} (type: ${sqlDPs[id].type}, ID: ${sqlDPs[id].index}) and state: ${JSON.stringify(state)}`); - - prepareTaskCheckTypeAndDbId(id, state, isCounter, err => { - if (!sqlDPs[id]) { - return cb && cb() - } - adapter.log.debug(`pushValueIntoDB-prepareTaskCheckTypeAndDbId RESULT for ${id} (type: ${sqlDPs[id].type}, ID: ${sqlDPs[id].index}) and state: ${JSON.stringify(state)}: ${err}`); - if (err) { - return cb && cb(err); - } - - const type = sqlDPs[id].type; - - // increase timestamp if last is the same - if (!isCounter && sqlDPs[id].ts && state.ts === sqlDPs[id].ts) { - state.ts++; - } - - // remember last timestamp - sqlDPs[id].ts = state.ts; - - // if it was not deleted in this time - sqlDPs[id].list = sqlDPs[id].list || []; - - sqlDPs[id].list.push({state, from: from[state.from] || 0, db: isCounter ? 'ts_counter' : dbNames[type]}); - - const _settings = sqlDPs[id][adapter.namespace] || {}; - const maxLength = _settings.maxLength !== undefined ? _settings.maxLength : parseInt(adapter.config.maxLength, 10) || 0; - if ((cb || _settings && sqlDPs[id].list.length > maxLength) && !storeInCacheOnly) { - storeCached(id, cb); - } else if (cb && storeInCacheOnly) { - setImmediate(cb); - } - }); -} - -function storeCached(onlyId, cb) { - let count = 0; - for (const id in sqlDPs) { - if (!sqlDPs.hasOwnProperty(id) || (onlyId !== undefined && onlyId !== id)) { - continue; - } - - const _settings = sqlDPs[id][adapter.namespace] || {}; - if (_settings && sqlDPs[id].list && sqlDPs[id].list.length) { - _settings.enableDebugLogs && adapter.log.debug(`inserting ${sqlDPs[id].list.length} entries from ${id} to DB`); - const inFlightId = `${id}_${Date.now()}_${Math.random()}`; - sqlDPs[id].inFlight = sqlDPs[id].inFlight || {}; - sqlDPs[id].inFlight[inFlightId] = sqlDPs[id].list; - sqlDPs[id].list = []; - count++; - pushValuesIntoDB(id, sqlDPs[id].inFlight[inFlightId], err => { - sqlDPs[id] && sqlDPs[id].inFlight && sqlDPs[id].inFlight[inFlightId] && delete sqlDPs[id].inFlight[inFlightId]; - if (!--count && cb) { - cb(err); - cb = null; - } - }); - if (onlyId !== undefined) { - break; - } - } - } - if (!count && cb) { - cb(); - } -} - -function pushValuesIntoDB(id, list, cb) { - if (!list.length) { - return cb && setImmediate(cb); - } - - if (!multiRequests) { - if (tasks.length > MAXTASKS) { - const error = `Cannot queue new requests, because more than ${MAXTASKS}`; - adapter.log.error(error); - cb && cb(error); - } else { - tasks.push({operation: 'insert', index: sqlDPs[id].index, list, id, callback: cb}); - tasks.length === 1 && processTasks(); - } - } else { - const query = SQLFuncs.insert(adapter.config.dbname, sqlDPs[id].index, list); - _insertValueIntoDB(query, id, cb); - } - -} - -let lockTasks = false; -function processTasks() { - if (lockTasks) { - return adapter.log.debug('Tries to execute task, but last one not finished!'); - } - - lockTasks = true; - - if (tasks.length) { - if (tasks[0].operation === 'query') { - _executeQuery(tasks[0].query, tasks[0].id, () => { - tasks[0].callback && tasks[0].callback(); - tasks.shift(); - lockTasks = false; - tasks.length && setTimeout(processTasks, adapter.config.requestInterval); - }); - } else if (tasks[0].operation === 'insert') { - const callbacks = []; - if (tasks[0].callback) { - callbacks.push(tasks[0].callback); - } - for (let i = 1; i < tasks.length; i++) { - if (tasks[i].operation === 'insert' && tasks[0].index === tasks[i].index) { - tasks[0].list = tasks[0].list.concat(tasks[i].list); - if (tasks[i].callback) { - callbacks.push(tasks[i].callback); - } - tasks.splice(i, 1); - i--; - } - } - const query = SQLFuncs.insert(adapter.config.dbname, tasks[0].index, tasks[0].list); - _insertValueIntoDB(query, tasks[0].id, () => { - callbacks.forEach(cb => cb()); - tasks.shift(); - lockTasks = false; - tasks.length && setTimeout(processTasks, adapter.config.requestInterval); - }); - } else if (tasks[0].operation === 'select') { - _getDataFromDB(tasks[0].query, tasks[0].options, (err, rows) => { - tasks[0].callback && tasks[0].callback(err, rows); - tasks.shift(); - lockTasks = false; - tasks.length && setTimeout(processTasks, adapter.config.requestInterval); - }); - } else if (tasks[0].operation === 'userQuery') { - _userQuery(tasks[0].msg, () => { - tasks[0].callback && tasks[0].callback(); - tasks.shift(); - lockTasks = false; - tasks.length && setTimeout(processTasks, adapter.config.requestInterval); - }); - } else if (tasks[0].operation === 'delete') { - _checkRetention(tasks[0].query, () => { - tasks[0].callback && tasks[0].callback(); - tasks.shift(); - lockTasks = false; - tasks.length && setTimeout(processTasks, adapter.config.requestInterval); - }); - } else { - adapter.log.error(`unknown task: ${tasks[0].operation}`); - tasks[0].callback && tasks[0].callback(); - tasks.shift(); - lockTasks = false; - tasks.length && setTimeout(processTasks, adapter.config.requestInterval); - } - } -} - -// may be it is required to cache all the data in memory -function getId(id, type, cb) { - let query = SQLFuncs.getIdSelect(adapter.config.dbname, id); - adapter.log.debug(query); - - borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - return cb && cb(err, id); - } - - client.execute(query, (err, rows /* , fields */) => { - if (!sqlDPs[id]) { - returnClientToPool(client); - return cb && cb(new Error(`ID ${id} no longer active`), id); - } - if (rows && rows.rows) { - rows = rows.rows; - } - - if (err) { - returnClientToPool(client); - adapter.log.error(`Cannot select ${query}: ${err}`); - return cb && cb(err, id); - } else if (!rows.length) { - if (type !== null && type !== undefined) { - // insert - query = SQLFuncs.getIdInsert(adapter.config.dbname, id, type); - - adapter.log.debug(query); - - client.execute(query, (err /* , rows, fields */) => { - if (err) { - returnClientToPool(client); - adapter.log.error(`Cannot insert ${query}: ${err}`); - cb && cb(err, id); - } else { - query = SQLFuncs.getIdSelect(adapter.config.dbname,id); - - adapter.log.debug(query); - - client.execute(query, (err, rows /* , fields */) => { - returnClientToPool(client); - if (rows && rows.rows) { - rows = rows.rows; - } - - if (err) { - adapter.log.error(`Cannot select ${query}: ${err}`); - cb && cb(err, id); - } else if (rows[0]) { - sqlDPs[id].index = rows[0].id; - sqlDPs[id].type = rows[0].type; - - cb && cb(null, id); - } else { - adapter.log.error(`No result for select ${query}: after insert`); - cb && cb(new Error(`No result for select ${query}: after insert`), id); - } - }); - } - }); - } else { - returnClientToPool(client); - cb && cb('id not found', id); - } - } else { - sqlDPs[id].index = rows[0].id; - if (rows[0].type === null || typeof rows[0].type !== 'number') { - sqlDPs[id].type = type; - - const query = SQLFuncs.getIdUpdate(adapter.config.dbname, sqlDPs[id].index, sqlDPs[id].type); - - adapter.log.debug(query); - - client.execute(query, (err, rows /* , fields */) => { - returnClientToPool(client); - if (err) { - adapter.log.error(`error updating history config for ${id} to pin datatype: ${query}: ${err}`); - } else { - adapter.log.info(`changed history configuration to pin detected datatype for ${id}`); - } - cb && cb(null, id); - }); - } else { - returnClientToPool(client); - - sqlDPs[id].type = rows[0].type; - - cb && cb(null, id); - } - } - }); - }); -} - -// my be it is required to cache all the data in memory -function getFrom(_from, cb) { - // const sources = (adapter.config.dbtype !== 'postgresql' ? (adapter.config.dbname + '.') : '') + 'sources'; - let query = SQLFuncs.getFromSelect(adapter.config.dbname, _from); - adapter.log.debug(query); - - borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - return cb && cb(err, _from); - } - client.execute(query, (err, rows /* , fields */) => { - if (rows && rows.rows) rows = rows.rows; - if (err) { - returnClientToPool(client); - adapter.log.error(`Cannot select ${query}: ${err}`); - return cb && cb(err, _from); - } - if (!rows.length) { - // insert - query = SQLFuncs.getFromInsert(adapter.config.dbname, _from); - adapter.log.debug(query); - client.execute(query, (err /* , rows, fields */) => { - if (err) { - returnClientToPool(client); - adapter.log.error(`Cannot insert ${query}: ${err}`); - return cb && cb(err, _from); - } - - query = SQLFuncs.getFromSelect(adapter.config.dbname, _from); - adapter.log.debug(query); - client.execute(query, (err, rows /* , fields */) => { - returnClientToPool(client); - - if (rows && rows.rows) rows = rows.rows; - if (err) { - adapter.log.error(`Cannot select ${query}: ${err}`); - return cb && cb(err, _from); - } - from[_from] = rows[0].id; - - cb && cb(null, _from); - }); - }); - } else { - returnClientToPool(client); - - from[_from] = rows[0].id; - - cb && cb(null, _from); - } - }); - }); -} - -function sortByTs(a, b) { - const aTs = a.ts; - const bTs = b.ts; - return aTs < bTs ? -1 : (aTs > bTs ? 1 : 0); -} - - -function getOneCachedData(id, options, cache, addId) { - addId = addId || options.addId; - - if (sqlDPs[id]) { - let res = []; - for (let inFlightId in sqlDPs[id].inFlight) { - adapter.log.debug(`getOneCachedData: add ${sqlDPs[id].inFlight[inFlightId].length} inFlight datapoints for ${options.index || options.id}`); - res = res.concat(sqlDPs[id].inFlight[inFlightId]); - } - res = res.concat(sqlDPs[id].list); - // todo can be optimized - if (res) { - let iProblemCount = 0; - let vLast = null; - for (let i = res.length - 1; i >= 0 ; i--) { - if (!res[i] || !res[i].state) { - iProblemCount++; - continue; - } - if (options.start && res[i].state.ts < options.start) { - // add one before start - cache.unshift(Object.assign({}, res[i].state)); - break; - } else if (res[i].state.ts > options.end) { - // add one after end - vLast = res[i].state; - continue; - } - - if (vLast) { - cache.unshift(Object.assign({}, vLast)); - vLast = null; - } - - cache.unshift(Object.assign({}, res[i].state)); - - if ((options.returnNewestEntries && options.count && cache.length >= options.count) && (options.aggregate === 'onchange' || options.aggregate === '' || options.aggregate === 'none')) { - break; - } - } - - iProblemCount && adapter.log.warn(`getOneCachedData: got null states ${iProblemCount} times for ${options.index || options.id}`); - - adapter.log.debug(`getOneCachedData: got ${res.length} datapoints for ${options.index || options.id}`); - } else { - adapter.log.debug(`getOneCachedData: datapoints for ${options.index || options.id} do not yet exist`); - } - } -} - -function getCachedData(options, callback) { - const cache = []; - - if (options.index || options.id) { - getOneCachedData(options.index || options.id, options, cache); - } else { - for (const id in sqlDPs) { - if (sqlDPs.hasOwnProperty(id)) { - getOneCachedData(id, options, cache, true); - } - } - } - - let earliestTs = null; - for (let c = 0; c < cache.length; c++) { - if (typeof cache[c].ts === 'string') { - cache[c].ts = parseInt(cache[c].ts, 10); - } - - if (adapter.common.loglevel === 'debug') { - cache[c].date = new Date(parseInt(cache[c].ts, 10)); - } - if (options.ack) { - cache[c].ack = !!cache[c].ack; - } - if (typeof cache[c].val === 'number' && isFinite(cache[c].val) && options.round) { - cache[c].val = Math.round(cache[c].val * options.round) / options.round; - } - if (sqlDPs[options.index || options.id] && sqlDPs[options.index || options.id].type === 2) { // 2 === boolean - cache[c].val = !!cache[c].val; - } - if (options.addId && !cache[c].id && options.id) { - cache[c].id = options.index || options.id; - } - if (cache[c].ts < earliestTs || earliestTs === null) { - earliestTs = cache[c].ts; - } - } - - options.length = cache.length; - callback(cache, options.returnNewestEntries && options.count && cache.length >= options.count, !!(sqlDPs[options.index || options.id] && sqlDPs[options.index || options.id].inFlight && Object.keys(sqlDPs[options.index || options.id].inFlight).length), earliestTs); -} - - -function _getDataFromDB(query, options, callback) { - adapter.log.debug(query); - - borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - return callback && callback(err); - } - client.execute(query, (err, rows /* , fields */) => { - returnClientToPool(client); - - if (!err && rows && rows.rows) rows = rows.rows; - - if (!err && rows) { - for (let c = 0; c < rows.length; c++) { - if (typeof rows[c].ts === 'string') { - rows[c].ts = parseInt(rows[c].ts, 10); - } - - if (adapter.common.loglevel === 'debug') { - rows[c].date = new Date(parseInt(rows[c].ts, 10)); - } - if (options.ack) { - rows[c].ack = !!rows[c].ack; - } - if (typeof rows[c].val === 'number' && isFinite(rows[c].val) && options.round) { - rows[c].val = Math.round(rows[c].val * options.round) / options.round; - } - if (sqlDPs[options.index || options.id] && sqlDPs[options.index || options.id].type === 2) { // 2 === boolean - rows[c].val = !!rows[c].val; - } - if (options.addId && !rows[c].id && options.id) { - rows[c].id = options.index || options.id; - } - } - } - callback && callback(err, rows); - }); - }); -} - -function getDataFromDB(db, options, callback) { - const query = SQLFuncs.getHistory(adapter.config.dbname, db, options); - adapter.log.debug(query); - if (!multiRequests) { - if (tasks.length > MAXTASKS) { - adapter.log.error(`Cannot queue new requests, because more than ${MAXTASKS}`); - callback && callback(`Cannot queue new requests, because more than ${MAXTASKS}`); - } else { - tasks.push({operation: 'select', query, options, callback}); - tasks.length === 1 && processTasks(); - } - } else { - _getDataFromDB(query, options, callback); - } -} - -function getCounterDataFromDB(options, callback) { - const query = SQLFuncs.getCounterDiff(adapter.config.dbname, options); - - adapter.log.debug(query); - - if (!multiRequests) { - if (tasks.length > MAXTASKS) { - const error = `Cannot queue new requests, because more than ${MAXTASKS}`; - adapter.log.error(error); - callback && callback(error); - } else { - tasks.push({operation: 'select', query, options, callback}); - tasks.length === 1 && processTasks(); - } - } else { - _getDataFromDB(query, options, callback); - } -} - -function getCounterDiff(msg) { - const startTime = Date.now(); - const id = msg.message.id; - const start = msg.message.options.start || 0; - const end = msg.message.options.end || (Date.now() + 5000000); - - - if (!sqlDPs[id]) { - adapter.sendTo(msg.from, msg.command, {result: [], step: null, error: 'Not enabled'}, msg.callback); - } else { - if (!SQLFuncs.getCounterDiff) { - adapter.sendTo(msg.from, msg.command, {result: [], step: null, error: 'Counter option is not enabled for this type of SQL'}, msg.callback); - } else { - const options = {id: sqlDPs[id].index, start, end, index: id}; - getCounterDataFromDB(options, (err, data) => - commons.sendResponseCounter(adapter, msg, options, (err ? err.toString() : null) || data, startTime)); - } - } -} - -function getHistory(msg) { - const startTime = Date.now(); - - if (!msg.message || !msg.message.options) { - return adapter.sendTo(msg.from, msg.command, { - error: 'Invalid call. No options for getHistory provided' - }, msg.callback); - } - - const options = { - id: msg.message.id === '*' ? null : msg.message.id, - start: msg.message.options.start, - end: msg.message.options.end || (Date.now() + 5000000), - step: parseInt(msg.message.options.step, 10) || null, - count: parseInt(msg.message.options.count, 10), - ignoreNull: msg.message.options.ignoreNull, - aggregate: msg.message.options.aggregate || 'average', // One of: max, min, average, total, none, on-change - limit: parseInt(msg.message.options.limit, 10) || parseInt(msg.message.options.count, 10) || adapter.config.limit || 2000, - from: msg.message.options.from || false, - q: msg.message.options.q || false, - ack: msg.message.options.ack || false, - ms: msg.message.options.ms || false, - addId: msg.message.options.addId || false, - sessionId: msg.message.options.sessionId, - returnNewestEntries: msg.message.options.returnNewestEntries || false, - percentile: msg.message.options.aggregate === 'percentile' ? parseInt(msg.message.options.percentile, 10) || 50 : null, - quantile: msg.message.options.aggregate === 'quantile' ? parseFloat(msg.message.options.quantile) || 0.5 : null, - integralUnit: msg.message.options.aggregate === 'integral' ? parseInt(msg.message.options.integralUnit, 10) || 60 : null, - integralInterpolation: msg.message.options.aggregate === 'integral' ? msg.message.options.integralInterpolation || 'none' : null, - removeBorderValues: msg.message.options.removeBorderValues || false, - logId: (msg.message.id ? msg.message.id : 'all') + Date.now() + Math.random() - }; - - adapter.log.debug(`${options.logId} getHistory message: ${JSON.stringify(msg.message)}`); - - if (!options.count || isNaN(options.count)) { - if (options.aggregate === 'none' || options.aggregate === 'onchange') { - options.count = options.limit; - } else { - options.count = 500; - } - } - - try { - if (options.start && typeof options.start !== 'number') { - options.start = new Date(options.start).getTime(); - } - } catch (err) { - return adapter.sendTo(msg.from, msg.command, { - error: `Invalid call. Start date ${JSON.stringify(options.start)} is not a valid date` - }, msg.callback); - } - - try { - if (options.end && typeof options.end !== 'number') { - options.end = new Date(options.end).getTime(); - } - } catch (err) { - return adapter.sendTo(msg.from, msg.command, { - error: `Invalid call. End date ${JSON.stringify(options.end)} is not a valid date` - }, msg.callback); - } - - if (!options.start && options.count) { - options.returnNewestEntries = true; - } - - if (msg.message.options.round !== null && msg.message.options.round !== undefined && msg.message.options.round !== '') { - msg.message.options.round = parseInt(msg.message.options.round, 10); - if (!isFinite(msg.message.options.round) || msg.message.options.round < 0) { - options.round = adapter.config.round; - } else { - options.round = Math.pow(10, parseInt(msg.message.options.round, 10)); - } - } else { - options.round = adapter.config.round; - } - - if (options.id && aliasMap[options.id]) { - options.id = aliasMap[options.id]; - } - - if (options.aggregate === 'percentile' && options.percentile < 0 || options.percentile > 100) { - adapter.log.error(`Invalid percentile value: ${options.percentile}, use 50 as default`); - options.percentile = 50; - } - - if (options.aggregate === 'quantile' && options.quantile < 0 || options.quantile > 1) { - adapter.log.error(`Invalid quantile value: ${options.quantile}, use 0.5 as default`); - options.quantile = 0.5; - } - - if (options.aggregate === 'integral' && (typeof options.integralUnit !== 'number' || options.integralUnit <= 0)) { - adapter.log.error(`Invalid integralUnit value: ${options.integralUnit}, use 60s as default`); - options.integralUnit = 60; - } - - sqlDPs[options.id] = sqlDPs[options.id] || {}; - const debugLog = options.debugLog = !!((sqlDPs[options.id] && sqlDPs[options.id][adapter.namespace] && sqlDPs[options.id][adapter.namespace].enableDebugLogs) || adapter.config.enableDebugLogs); - - if (options.ignoreNull === 'true') options.ignoreNull = true; // include nulls and replace them with last value - if (options.ignoreNull === 'false') options.ignoreNull = false; // include nulls - if (options.ignoreNull === '0') options.ignoreNull = 0; // include nulls and replace them with 0 - if (options.ignoreNull !== true && options.ignoreNull !== false && options.ignoreNull !== 0) { - options.ignoreNull = false; - } - - if (options.start > options.end) { - const _end = options.end; - options.end = options.start; - options.start =_end; - } - - if (!options.start && !options.count) { - options.start = Date.now() - 86400000; // - 1 day - } - - debugLog && adapter.log.debug(`${options.logId} getHistory options final: ${JSON.stringify(options)}`); - - if (sqlDPs[options.id].type === undefined && sqlDPs[options.id].dbtype !== undefined) { - if (sqlDPs[options.id][adapter.namespace] && sqlDPs[options.id][adapter.namespace].storageType) { - if (storageTypes.indexOf(sqlDPs[options.id][adapter.namespace].storageType) === sqlDPs[options.id].dbtype) { - debugLog && adapter.log.debug(`${options.logId} For getHistory for id ${options.id}: Type empty, use storageType dbtype ${sqlDPs[options.id].dbtype}`); - sqlDPs[options.id].type = sqlDPs[options.id].dbtype; - } - } - else { - debugLog && adapter.log.debug(`${options.logId} For getHistory for id ${options.id}: Type empty, use dbtype ${sqlDPs[options.id].dbtype}`); - sqlDPs[options.id].type = sqlDPs[options.id].dbtype; - } - } - if (options.id && sqlDPs[options.id].index === undefined) { - // read or create in DB - return getId(options.id, null, err => { - if (err) { - adapter.log.warn(`Cannot get index of "${options.id}": ${err}`); - commons.sendResponse(adapter, msg, options, [], startTime); - } else { - getHistory(msg); - } - }); - } - if (options.id && sqlDPs[options.id].type === undefined) { - adapter.log.warn(`For getHistory for id "${options.id}": Type empty. Need to write data first. Index = ${sqlDPs[options.id].index}`); - commons.sendResponse(adapter, msg, options, 'Please wait till next data record is logged and reload.', startTime); - return; - } - - const type = sqlDPs[options.id].type; - if (options.id) { - options.index = options.id; - options.id = sqlDPs[options.id].index; - } - - if (options.debugLog) { - options.log = adapter.log.debug; - } - - // if specific id requested - if (options.id) { - getCachedData(options, (cacheData, isFull, includesInFlightData, earliestTs) => { - debugLog && adapter.log.debug(`${options.logId} after getCachedData: length = ${cacheData.length}, isFull=${isFull}`); - - // if all data read - if ((isFull && cacheData.length) && (options.aggregate === 'onchange' || options.aggregate === '' || options.aggregate === 'none')) { - cacheData = cacheData.sort(sortByTs); - if (options.count && cacheData.length > options.count && options.aggregate === 'none') { - cacheData.splice(0, cacheData.length - options.count); - debugLog && adapter.log.debug(`${options.logId} cut cacheData to ${options.count} values`); - } - adapter.log.debug(`${options.logId} Send: ${cacheData.length} values in: ${Date.now() - startTime}ms`); - - adapter.sendTo(msg.from, msg.command, { - result: cacheData, - step: null, - error: null - }, msg.callback); - } else { - const origEnd = options.end; - if (includesInFlightData) { - options.end = earliestTs; - } - // if not all data read - getDataFromDB(dbNames[type], options, (err, data) => { - if (!err && data) { - options.end = origEnd; - if (options.aggregate === 'none' && options.count && options.returnNewestEntries) { - cacheData = cacheData.reverse() - data = cacheData.concat(data); - } else { - data = data.concat(cacheData); - } - debugLog && adapter.log.debug(`${options.logId} after getDataFromDB: length = ${data.length}`); - if (options.count && data.length > options.count && options.aggregate === 'none' && !options.returnNewestEntries) { - if (options.start) { - for (let i = 0; i < data.length; i++) { - if (data[i].ts < options.start) { - data.splice(i, 1); - i--; - } else { - break; - } - } - } - data.splice(options.count); - options.debugLog && adapter.log.debug(`${options.logId} pre-cut data to ${options.count} oldest values`); - } - - data.sort(sortByTs); - } - try { - commons.sendResponse(adapter, msg, options, (err ? err.toString() : null) || data, startTime) - } catch (e) { - commons.sendResponse(adapter, msg, options, e.toString(), startTime); - } - }); - } - }); - } else { - // if all IDs requested - let rows = []; - let count = 0; - getCachedData(options, (cacheData, isFull, includesInFlightData, earliestTs) => { - if (isFull && cacheData.length) { - cacheData.sort(sortByTs); - commons.sendResponse(adapter, msg, options, cacheData, startTime); - } else { - if (includesInFlightData) { - options.end = earliestTs; - } - for (let db = 0; db < dbNames.length; db++) { - count++; - getDataFromDB(dbNames[db], options, (err, data) => { - if (data) { - rows = rows.concat(data); - } - if (!--count) { - rows.sort(sortByTs); - try { - commons.sendResponse(adapter, msg, options, rows, startTime); - } catch (e) { - commons.sendResponse(adapter, msg, options, e.toString(), startTime); - } - } - }); - } - } - }); - } -} - -function update(id, state, cb) { - // first try to find the value in not yet saved data - let found = false; - if (sqlDPs[id]) { - const res = sqlDPs[id].list; - if (res) { - for (let i = res.length - 1; i >= 0; i--) { - if (res[i].state.ts === state.ts) { - if (state.val !== undefined) { - res[i].state.val = state.val; - } - if (state.q !== undefined && res[i].q !== undefined) { - res[i].state.q = state.q; - } - if (state.from !== undefined && res[i].from !== undefined) { - res[i].state.from = state.from; - } - if (state.ack !== undefined) { - res[i].state.ack = state.ack; - } - found = true; - break; - } - } - } - } - - if (!found) { - prepareTaskCheckTypeAndDbId(id, state, false, err => { - if (err) { - return cb && cb(err); - } - - const type = sqlDPs[id].type; - - const query = SQLFuncs.update(adapter.config.dbname, sqlDPs[id].index, state, from[state.from], dbNames[type]); - - if (!multiRequests) { - if (tasks.length > MAXTASKS) { - const error = `Cannot queue new requests, because more than ${MAXTASKS}`; - adapter.log.error(error); - cb && cb(error); - } else { - tasks.push({operation: 'query', query, id, callback: cb}); - tasks.length === 1 && processTasks(); - } - } else { - _executeQuery(query, id, cb); - } - }); - } else { - cb && cb(); - } -} - -function _delete(id, state, cb) { - // first try to find the value in not yet saved data - let found = false; - if (sqlDPs[id]) { - const res = sqlDPs[id].list; - if (res) { - if (!state.ts && !state.start && !state.end) { - sqlDPs[id].list = []; - } else { - for (let i = res.length - 1; i >= 0; i--) { - if (state.start && state.end) { - if (res[i].state.ts >= state.start && res[i].state.ts <= state.end) { - res.splice(i, 1); - } - } else if (state.start) { - if (res[i].state.ts >= state.start) { - res.splice(i, 1); - } - } else if (state.end) { - if (res[i].state.ts <= state.end) { - res.splice(i, 1); - } - } else - if (res[i].state.ts === state.ts) { - res.splice(i, 1); - found = true; - break; - } - } - } - } - } - - if (!found) { - prepareTaskCheckTypeAndDbId(id, state, false, err => { - if (err) { - return cb && cb(err); - } - - const type = sqlDPs[id].type; - - let query; - if (state.start && state.end) { - query = SQLFuncs.delete(adapter.config.dbname, dbNames[type], sqlDPs[id].index, state.start, state.end); - } else if (state.ts) { - query = SQLFuncs.delete(adapter.config.dbname, dbNames[type], sqlDPs[id].index, state.ts); - } else { - query = SQLFuncs.delete(adapter.config.dbname, dbNames[type], sqlDPs[id].index); - } - - if (!multiRequests) { - if (tasks.length > MAXTASKS) { - const error = `Cannot queue new requests, because more than ${MAXTASKS}`; - adapter.log.error(error); - cb && cb(error); - } else { - tasks.push({operation: 'query', query, id, callback: cb}); - tasks.length === 1 && processTasks(); - } - } else { - _executeQuery(query, id, cb); - } - }); - } else { - cb && cb(); - } -} - -function updateState(msg) { - if (!msg.message) { - adapter.log.error('updateState called with invalid data'); - return adapter.sendTo(msg.from, msg.command, { - error: `Invalid call: ${JSON.stringify(msg)}` - }, msg.callback); - } - let id; - if (Array.isArray(msg.message)) { - adapter.log.debug(`updateState ${msg.message.length} items`); - for (let i = 0; i < msg.message.length; i++) { - id = aliasMap[msg.message[i].id] ? aliasMap[msg.message[i].id] : msg.message[i].id; - - if (msg.message[i].state && typeof msg.message[i].state === 'object') { - update(id, msg.message[i].state); - } else { - adapter.log.warn(`Invalid state for ${JSON.stringify(msg.message[i])}`); - } - } - } else if (msg.message.state && Array.isArray(msg.message.state)) { - adapter.log.debug(`updateState ${msg.message.state.length} items`); - id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; - for (let j = 0; j < msg.message.state.length; j++) { - if (msg.message.state[j] && typeof msg.message.state[j] === 'object') { - update(id, msg.message.state[j]); - } else { - adapter.log.warn(`Invalid state for ${JSON.stringify(msg.message.state[j])}`); - } - } - } else if (msg.message.id && msg.message.state && typeof msg.message.state === 'object') { - adapter.log.debug('updateState 1 item'); - id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; - return update(id, msg.message.state, () => adapter.sendTo(msg.from, msg.command, { - success: true, - connected: !!clientPool - }, msg.callback)); - } else { - adapter.log.error('updateState called with invalid data'); - return adapter.sendTo(msg.from, msg.command, { - error: `Invalid call: ${JSON.stringify(msg)}` - }, msg.callback); - } - - adapter.sendTo(msg.from, msg.command, { - success: true, - connected: !!clientPool - }, msg.callback); -} - -function deleteState(msg) { - if (!msg.message) { - adapter.log.error('deleteState called with invalid data'); - return adapter.sendTo(msg.from, msg.command, { - error: `Invalid call: ${JSON.stringify(msg)}` - }, msg.callback); - } - let id; - if (Array.isArray(msg.message)) { - adapter.log.debug(`deleteState ${msg.message.length} items`); - for (let i = 0; i < msg.message.length; i++) { - id = aliasMap[msg.message[i].id] ? aliasMap[msg.message[i].id] : msg.message[i].id; - - // {id: 'blabla', ts: 892} - if (msg.message[i].ts) { - _delete(id, {ts: msg.message[i].ts}); - } else - if (msg.message[i].start) { - if (typeof msg.message[i].start === 'string') { - msg.message[i].start = new Date(msg.message[i].start).getTime(); - } - if (typeof msg.message[i].end === 'string') { - msg.message[i].end = new Date(msg.message[i].end).getTime(); - } - _delete(id, {start: msg.message[i].start, end: msg.message[i].end || Date.now()}); - } else - if (typeof msg.message[i].state === 'object' && msg.message[i].state && msg.message[i].state.ts) { - _delete(id, {ts: msg.message[i].state.ts}); - } else - if (typeof msg.message[i].state === 'object' && msg.message[i].state && msg.message[i].state.start) { - if (typeof msg.message[i].state.start === 'string') { - msg.message[i].state.start = new Date(msg.message[i].state.start).getTime(); - } - if (typeof msg.message[i].state.end === 'string') { - msg.message[i].state.end = new Date(msg.message[i].state.end).getTime(); - } - _delete(id, {start: msg.message[i].state.start, end: msg.message[i].state.end || Date.now()}); - } else { - adapter.log.warn(`Invalid state for ${JSON.stringify(msg.message[i])}`); - } - } - } else if (msg.message.state && Array.isArray(msg.message.state)) { - adapter.log.debug(`deleteState ${msg.message.state.length} items`); - id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; - - for (let j = 0; j < msg.message.state.length; j++) { - if (msg.message.state[j] && typeof msg.message.state[j] === 'object') { - if (msg.message.state[j].ts) { - _delete(id, {ts: msg.message.state[j].ts}); - } else if (msg.message.state[j].start) { - if (typeof msg.message.state[j].start === 'string') { - msg.message.state[j].start = new Date(msg.message.state[j].start).getTime(); - } - if (typeof msg.message.state[j].end === 'string') { - msg.message.state[j].end = new Date(msg.message.state[j].end).getTime(); - } - _delete(id, {start: msg.message.state[j].start, end: msg.message.state[j].end || Date.now()}); - } - } else if (msg.message.state[j] && typeof msg.message.state[j] === 'number') { - _delete(id, {ts: msg.message.state[j]}); - } else { - adapter.log.warn(`Invalid state for ${JSON.stringify(msg.message.state[j])}`); - } - } - } else if (msg.message.ts && Array.isArray(msg.message.ts)) { - adapter.log.debug(`deleteState ${msg.message.ts.length} items`); - id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; - for (let j = 0; j < msg.message.ts.length; j++) { - if (msg.message.ts[j] && typeof msg.message.ts[j] === 'number') { - _delete(id, {ts: msg.message.ts[j]}); - } else { - adapter.log.warn(`Invalid state for ${JSON.stringify(msg.message.ts[j])}`); - } - } - } else if (msg.message.id && msg.message.state && typeof msg.message.state === 'object') { - adapter.log.debug('deleteState 1 item'); - id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; - return _delete(id, {ts: msg.message.state.ts}, () => adapter.sendTo(msg.from, msg.command, { - success: true, - connected: !!clientPool - }, msg.callback)); - } else if (msg.message.id && msg.message.ts && typeof msg.message.ts === 'number') { - adapter.log.debug('deleteState 1 item'); - id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; - return _delete(id, {ts: msg.message.ts}, () => adapter.sendTo(msg.from, msg.command, { - success: true, - connected: !!clientPool - }, msg.callback)); - } else { - adapter.log.error('deleteState called with invalid data'); - return adapter.sendTo(msg.from, msg.command, {error: `Invalid call: ${JSON.stringify(msg)}`}, msg.callback); - } - - adapter.sendTo(msg.from, msg.command, { - success: true, - connected: !!clientPool - }, msg.callback); -} - -function deleteStateAll(msg) { - if (!msg.message) { - adapter.log.error('deleteState called with invalid data'); - return adapter.sendTo(msg.from, msg.command, { - error: `Invalid call: ${JSON.stringify(msg)}` - }, msg.callback); - } - let id; - if (Array.isArray(msg.message)) { - adapter.log.debug(`deleteStateAll ${msg.message.length} items`); - for (let i = 0; i < msg.message.length; i++) { - id = aliasMap[msg.message[i].id] ? aliasMap[msg.message[i].id] : msg.message[i].id; - _delete(id, {}); - } - } else if (msg.message.id) { - adapter.log.debug('deleteStateAll 1 item'); - id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; - return _delete(id, {}, () => adapter.sendTo(msg.from, msg.command, { - success: true, - connected: !!clientPool - }, msg.callback)); - } else { - adapter.log.error('deleteStateAll called with invalid data'); - return adapter.sendTo(msg.from, msg.command, {error: `Invalid call: ${JSON.stringify(msg)}`}, msg.callback); - } - - adapter.sendTo(msg.from, msg.command, { - success: true, - connected: !!clientPool - }, msg.callback); -} - -function storeStatePushData(id, state, applyRules) { - if (!state || typeof state !== 'object') { - throw new Error(`State ${JSON.stringify(state)} for ${id} is not valid`); - } - - let pushFunc = applyRules ? pushHistory : pushHelper; - if (!sqlDPs[id] || !sqlDPs[id][adapter.namespace]) { - if (applyRules) { - throw new Error(`sql not enabled for ${id}, so can not apply the rules as requested`); - } - sqlDPs[id] = sqlDPs[id] || {}; - sqlDPs[id].realId = id; - } - return new Promise((resolve, reject) => { - pushFunc(id, state , err => { - if (err) { - reject(new Error(`Error writing state for ${id}: ${err.message}, Data: ${JSON.stringify(state)}`)); - } else { - resolve(true); - } - }); - }); -} - -async function storeState(msg) { - if (!msg.message) { - adapter.log.error('storeState called with invalid data'); - return adapter.sendTo(msg.from, msg.command, { - error: `Invalid call: ${JSON.stringify(msg)}` - }, msg.callback); - } - - let errors = []; - let successCount = 0; - if (Array.isArray(msg.message)) { - adapter.log.debug(`storeState ${msg.message.length} items`); - for (let i = 0; i < msg.message.length; i++) { - const id = aliasMap[msg.message[i].id] ? aliasMap[msg.message[i].id] : msg.message[i].id; - try { - await storeStatePushData(id, msg.message[i].state, msg.message.rules); - successCount++; - } catch (err) { - errors.push(err.message); - } - } - } else if (msg.message.id && Array.isArray(msg.message.state)) { - adapter.log.debug(`storeState ${msg.message.state.length} items`); - const id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; - for (let j = 0; j < msg.message.state.length; j++) { - try { - await storeStatePushData(id, msg.message.state[j], msg.message.rules); - successCount++; - } catch (err) { - errors.push(err.message); - } - } - } else if (msg.message.id && msg.message.state) { - adapter.log.debug('storeState 1 item'); - const id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; - try { - await storeStatePushData(id, msg.message.state, msg.message.rules); - successCount++; - } catch (err) { - errors.push(err.message); - } - } else { - adapter.log.error('storeState called with invalid data'); - return adapter.sendTo(msg.from, msg.command, { - error: `Invalid call: ${JSON.stringify(msg)}` - }, msg.callback); - } - if (errors.length) { - adapter.log.warn(`storeState executed with ${errors.length} errors: ${errors.join(', ')}`); - return adapter.sendTo(msg.from, msg.command, { - error: `${errors.length} errors happened while storing data`, - errors: errors, - successCount - }, msg.callback); - } - - adapter.log.debug(`storeState executed with ${successCount} states successfully`); - - adapter.sendTo(msg.from, msg.command, { - success: true, - successCount, - connected: !!clientPool - }, msg.callback); -} - -function getDpOverview(msg) { - const result = {}; - const query = SQLFuncs.getIdSelect(adapter.config.dbname); - adapter.log.info(query); - - borrowClientFromPool((err, client) => { - if (err) { - returnClientToPool(client); - return adapter.sendTo(msg.from, msg.command, { - error: `Cannot select ${query}: ${err}` - }, msg.callback); - } - client.execute(query, (err, rows /* , fields */) => { - if (rows && rows.rows) { - rows = rows.rows; - } - if (err) { - returnClientToPool(client); - - adapter.log.error(`Cannot select ${query}: ${err}`); - - adapter.sendTo(msg.from, msg.command, { - error: `Cannot select ${query}: ${err}` - }, msg.callback); - return; - } - - adapter.log.info(`Query result ${JSON.stringify(rows)}`); - - if (rows.length) { - for (let r = 0; r < rows.length; r++) { - if (!result[rows[r].type]) { - result[rows[r].type] = {}; - } - - result[rows[r].type][rows[r].id] = {}; - - result[rows[r].type][rows[r].id].name = rows[r].name; - - switch (dbNames[rows[r].type]) { - case 'ts_number': - result[rows[r].type][rows[r].id].type = 'number'; - break; - - case 'ts_string': - result[rows[r].type][rows[r].id].type = 'string'; - break; - - case 'ts_bool': - result[rows[r].type][rows[r].id].type = 'boolean'; - break; - } - } - - adapter.log.info(`initialisation result: ${JSON.stringify(result)}`); - getFirstTsForIds(client, 0, result, msg); - } - }); - }); -} - -function getFirstTsForIds(dbClient, typeId, resultData, msg) { - if (typeId < dbNames.length) { - if (!resultData[typeId]) { - getFirstTsForIds(dbClient, typeId + 1, resultData, msg); - } else { - const query = SQLFuncs.getFirstTs(adapter.config.dbname, dbNames[typeId]); - adapter.log.info(query); - - dbClient.execute(query, (err, rows /* , fields */) => { - if (rows && rows.rows) { - rows = rows.rows; - } - - if (err) { - returnClientToPool(dbClient); - - adapter.log.error(`Cannot select ${query}: ${err}`); - adapter.sendTo(msg.from, msg.command, { - error: `Cannot select ${query}: ${err}` - }, msg.callback); - return; - } - - adapter.log.info(`Query result ${JSON.stringify(rows)}`); - - if (rows.length) { - for (let r = 0; r < rows.length; r++) { - if (resultData[typeId][rows[r].id]) { - resultData[typeId][rows[r].id].ts = rows[r].ts; - } - } - } - - adapter.log.info(`enhanced result (${typeId}): ${JSON.stringify(resultData)}`); - dpOverviewTimeout = setTimeout((dbClient, typeId, resultData, msg) => { - dpOverviewTimeout = null; - getFirstTsForIds(dbClient, typeId, resultData, msg); - }, 5000, dbClient, typeId + 1, resultData, msg); - }); - } - } else { - returnClientToPool(dbClient); - - adapter.log.info('consolidate data ...'); - const result = {}; - for (let ti = 0; ti < dbNames.length; ti++ ) { - if (resultData[ti]) { - for (let index in resultData[ti]) { - if (!resultData[ti].hasOwnProperty(index)) { - continue; - } - - const id = resultData[ti][index].name; - if (!result[id]) { - result[id] = {}; - result[id].type = resultData[ti][index].type; - result[id].ts = resultData[ti][index].ts; - } else { - result[id].type = 'undefined'; - if (resultData[ti][index].ts < result[id].ts) { - result[id].ts = resultData[ti][index].ts; - } - } - } - } - } - adapter.log.info(`Result: ${JSON.stringify(result)}`); - adapter.sendTo(msg.from, msg.command, { - success: true, - result: result - }, msg.callback); - } -} - -function enableHistory(msg) { - if (!msg.message || !msg.message.id) { - adapter.log.error('enableHistory called with invalid data'); - adapter.sendTo(msg.from, msg.command, { - error: 'Invalid call' - }, msg.callback); - return; - } - - const obj = {}; - obj.common = {}; - obj.common.custom = {}; - - if (msg.message.options) { - obj.common.custom[adapter.namespace] = msg.message.options; - } else { - obj.common.custom[adapter.namespace] = {}; - } - - obj.common.custom[adapter.namespace].enabled = true; - - adapter.extendForeignObject(msg.message.id, obj, err => { - if (err) { - adapter.log.error(`enableHistory: ${err}`); - adapter.sendTo(msg.from, msg.command, { - error: err - }, msg.callback); - } else { - adapter.log.info(JSON.stringify(obj)); - adapter.sendTo(msg.from, msg.command, { - success: true - }, msg.callback); - } - }); -} - -function disableHistory(msg) { - if (!msg.message || !msg.message.id) { - adapter.log.error('disableHistory called with invalid data'); - return adapter.sendTo(msg.from, msg.command, { - error: 'Invalid call' - }, msg.callback); - } - - const obj = {}; - obj.common = {}; - obj.common.custom = {}; - obj.common.custom[adapter.namespace] = {}; - obj.common.custom[adapter.namespace].enabled = false; - - adapter.extendForeignObject(msg.message.id, obj, err => { - if (err) { - adapter.log.error(`disableHistory: ${err}`); - adapter.sendTo(msg.from, msg.command, { - error: err - }, msg.callback); - } else { - adapter.log.info(JSON.stringify(obj)); - adapter.sendTo(msg.from, msg.command, { - success: true - }, msg.callback); - } - }); -} - -function getEnabledDPs(msg) { - const data = {}; - for (const id in sqlDPs) { - if (sqlDPs.hasOwnProperty(id) && sqlDPs[id] && sqlDPs[id][adapter.namespace] && sqlDPs[id][adapter.namespace].enabled) { - data[sqlDPs[id].realId] = sqlDPs[id][adapter.namespace]; - } - } - - adapter.sendTo(msg.from, msg.command, data, msg.callback); -} - -function main() { - setConnected(false); - - // set default history if not yet set - adapter.getForeignObject('system.config', (err, obj) => { - if (obj && obj.common && !obj.common.defaultHistory) { - obj.common.defaultHistory = adapter.namespace; - adapter.setForeignObject('system.config', obj, err => { - if (err) { - adapter.log.error(`Cannot set default history instance: ${err}`); - } else { - adapter.log.info(`Set default history instance to "${adapter.namespace}"`); - } - }); - } - }); - - adapter.config.dbname = adapter.config.dbname || 'iobroker'; - - if (adapter.config.writeNulls === undefined) { - adapter.config.writeNulls = true; - } - - adapter.config.retention = parseInt(adapter.config.retention, 10) || 0; - if (adapter.config.retention === -1 ) { // Custom timeframe - adapter.config.retention = (parseInt(adapter.config.customRetentionDuration, 10) || 0) * 24 * 60 * 60; - } - adapter.config.debounce = parseInt(adapter.config.debounce, 10) || 0; - adapter.config.requestInterval = (adapter.config.requestInterval === undefined || adapter.config.requestInterval === null || adapter.config.requestInterval === '') ? 0 : parseInt(adapter.config.requestInterval, 10) || 0; - - if (adapter.config.changesRelogInterval !== null && adapter.config.changesRelogInterval !== undefined) { - adapter.config.changesRelogInterval = parseInt(adapter.config.changesRelogInterval, 10); - } else { - adapter.config.changesRelogInterval = 0; - } - - if (!clients[adapter.config.dbtype]) { - adapter.log.error(`Unknown DB type: ${adapter.config.dbtype}`); - adapter.stop(); - } - if (adapter.config.multiRequests !== undefined && adapter.config.dbtype !== 'SQLite3Client' && adapter.config.dbtype !== 'sqlite') { - clients[adapter.config.dbtype].multiRequests = adapter.config.multiRequests; - } - if (adapter.config.maxConnections !== undefined && adapter.config.dbtype !== 'SQLite3Client' && adapter.config.dbtype !== 'sqlite') { - adapter.config.maxConnections = parseInt(adapter.config.maxConnections, 10); - if (adapter.config.maxConnections !== 0 && !adapter.config.maxConnections) { - adapter.config.maxConnections = 100; - } - } else { - adapter.config.maxConnections = 1; // SQLite does not support multiple connections - } - - if (adapter.config.changesMinDelta !== null && adapter.config.changesMinDelta !== undefined) { - adapter.config.changesMinDelta = parseFloat(adapter.config.changesMinDelta.toString().replace(/,/g, '.')); - } else { - adapter.config.changesMinDelta = 0; - } - - if (adapter.config.blockTime !== null && adapter.config.blockTime !== undefined) { - adapter.config.blockTime = parseInt(adapter.config.blockTime, 10) || 0; - } else { - if (adapter.config.debounce !== null && adapter.config.debounce !== undefined) { - adapter.config.debounce = parseInt(adapter.config.debounce, 10) || 0; - } else { - adapter.config.blockTime = 0; - } - } - - if (adapter.config.debounceTime !== null && adapter.config.debounceTime !== undefined) { - adapter.config.debounceTime = parseInt(adapter.config.debounceTime, 10) || 0; - } else { - adapter.config.debounceTime = 0; - } - - multiRequests = clients[adapter.config.dbtype].multiRequests; - if (!multiRequests) { - adapter.config.writeNulls = false; - } - - adapter.config.port = parseInt(adapter.config.port, 10) || 0; - - if (adapter.config.round !== null && adapter.config.round !== undefined && adapter.config.round !== '') { - adapter.config.round = parseInt(adapter.config.round, 10); - if (!isFinite(adapter.config.round) || adapter.config.round < 0) { - adapter.config.round = null; - adapter.log.info(`Invalid round value: ${adapter.config.round} - ignore, do not round values`); - } else { - adapter.config.round = Math.pow(10, parseInt(adapter.config.round, 10)); - } - } else { - adapter.config.round = null; - } - - if (adapter.config.dbtype === 'postgresql' && !SQL.PostgreSQLClient) { - const postgres = require(`${__dirname}/lib/postgresql-client`); - for (const attr in postgres) { - if (postgres.hasOwnProperty(attr) && !SQL[attr]) { - SQL[attr] = postgres[attr]; - } - } - } else if (adapter.config.dbtype === 'mssql' && !SQL.MSSQLClient) { - const mssql = require(`${__dirname}/lib/mssql-client`); - for (const attr_ in mssql) { - if (mssql.hasOwnProperty(attr_) && !SQL[attr_]) { - SQL[attr_] = mssql[attr_]; - } - } - } else if (adapter.config.dbtype === 'mysql' && !SQL.MySQL2Client) { - const mysql = require(`${__dirname}/lib/mysql-client`); - for (const attr_ in mysql) { - if (mysql.hasOwnProperty(attr_) && !SQL[attr_]) { - SQL[attr_] = mysql[attr_]; - } - } - } - SQLFuncs = require(`${__dirname}/lib/${adapter.config.dbtype}`); - - if (adapter.config.dbtype === 'sqlite' || adapter.config.host) { - connect(() => { - // read all custom settings - adapter.getObjectView('system', 'custom' , {}, (err, doc) => { - let count = 0; - if (doc && doc.rows) { - for (let i = 0, l = doc.rows.length; i < l; i++) { - if (doc.rows[i].value) { - let id = doc.rows[i].id; - const realId = id; - if (doc.rows[i].value[adapter.namespace] && doc.rows[i].value[adapter.namespace].aliasId) { - aliasMap[id] = doc.rows[i].value[adapter.namespace].aliasId; - adapter.log.debug(`Found Alias: ${id} --> ${aliasMap[id]}`); - id = aliasMap[id]; - } - - let storedIndex = null; - let storedType = null; - if (sqlDPs[id] && sqlDPs[id].index !== undefined) { - storedIndex = sqlDPs[id].index; - } - if (sqlDPs[id] && sqlDPs[id].dbtype !== undefined) { - storedType = sqlDPs[id].dbtype; - } - - sqlDPs[id] = doc.rows[i].value; - if (storedIndex !== null) { - sqlDPs[id].index = storedIndex; - } - if (storedType !== null) { - sqlDPs[id].dbtype = storedType; - } - - if (!sqlDPs[id][adapter.namespace] || typeof sqlDPs[id][adapter.namespace] !== 'object' || sqlDPs[id][adapter.namespace].enabled === false) { - delete sqlDPs[id]; - } else { - count++; - adapter.log.info(`enabled logging of ${id}, Alias=${id !== realId}, ${count} points now activated`); - - // maxLength - if (!sqlDPs[id][adapter.namespace].maxLength && sqlDPs[id][adapter.namespace].maxLength !== '0' && sqlDPs[id][adapter.namespace].maxLength !== 0) { - sqlDPs[id][adapter.namespace].maxLength = parseInt(adapter.config.maxLength, 10) || 0; - } else { - sqlDPs[id][adapter.namespace].maxLength = parseInt(sqlDPs[id][adapter.namespace].maxLength, 10); - } - - // retention - if (sqlDPs[id][adapter.namespace].retention !== undefined && sqlDPs[id][adapter.namespace].retention !== null && sqlDPs[id][adapter.namespace].retention !== '') { - sqlDPs[id][adapter.namespace].retention = parseInt(sqlDPs[id][adapter.namespace].retention, 10) || 0; - } else { - sqlDPs[id][adapter.namespace].retention = adapter.config.retention; - } - if (sqlDPs[id][adapter.namespace].retention === -1) { - // customRetentionDuration - if (sqlDPs[id][adapter.namespace].customRetentionDuration !== undefined && sqlDPs[id][adapter.namespace].customRetentionDuration !== null && sqlDPs[id][adapter.namespace].customRetentionDuration !== '') { - sqlDPs[id][adapter.namespace].customRetentionDuration = parseInt(sqlDPs[id][adapter.namespace].customRetentionDuration, 10) || 0; - } else { - sqlDPs[id][adapter.namespace].customRetentionDuration = adapter.config.customRetentionDuration; - } - sqlDPs[id][adapter.namespace].retention = sqlDPs[id][adapter.namespace].customRetentionDuration * 24 * 60 * 60 - } - - // debounceTime and debounce compatibility handling - if (!sqlDPs[id][adapter.namespace].blockTime && sqlDPs[id][adapter.namespace].blockTime !== '0' && sqlDPs[id][adapter.namespace].blockTime !== 0) { - if (!sqlDPs[id][adapter.namespace].debounce && sqlDPs[id][adapter.namespace].debounce !== '0' && sqlDPs[id][adapter.namespace].debounce !== 0) { - sqlDPs[id][adapter.namespace].blockTime = parseInt(adapter.config.blockTime, 10) || 0; - } else { - sqlDPs[id][adapter.namespace].blockTime = parseInt(sqlDPs[id][adapter.namespace].debounce, 10) || 0; - } - } else { - sqlDPs[id][adapter.namespace].blockTime = parseInt(sqlDPs[id][adapter.namespace].blockTime, 10) || 0; - } - if (!sqlDPs[id][adapter.namespace].debounceTime && sqlDPs[id][adapter.namespace].debounceTime !== '0' && sqlDPs[id][adapter.namespace].debounceTime !== 0) { - sqlDPs[id][adapter.namespace].debounceTime = parseInt(adapter.config.debounceTime, 10) || 0; - } else { - sqlDPs[id][adapter.namespace].debounceTime = parseInt(sqlDPs[id][adapter.namespace].debounceTime, 10) || 0; - } - - // changesOnly - sqlDPs[id][adapter.namespace].changesOnly = sqlDPs[id][adapter.namespace].changesOnly === 'true' || sqlDPs[id][adapter.namespace].changesOnly === true; - - // ignoreZero - sqlDPs[id][adapter.namespace].ignoreZero = sqlDPs[id][adapter.namespace].ignoreZero === 'true' || sqlDPs[id][adapter.namespace].ignoreZero === true; - - // round - if (sqlDPs[id][adapter.namespace].round !== null && sqlDPs[id][adapter.namespace].round !== undefined && sqlDPs[id][adapter.namespace] !== '') { - sqlDPs[id][adapter.namespace].round = parseInt(sqlDPs[id][adapter.namespace], 10); - if (!isFinite(sqlDPs[id][adapter.namespace].round) || sqlDPs[id][adapter.namespace].round < 0) { - sqlDPs[id][adapter.namespace].round = adapter.config.round; - } else { - sqlDPs[id][adapter.namespace].round = Math.pow(10, parseInt(sqlDPs[id][adapter.namespace].round, 10)); - } - } else { - sqlDPs[id][adapter.namespace].round = adapter.config.round; - } - - // ignoreAboveNumber - if (sqlDPs[id][adapter.namespace].ignoreAboveNumber !== undefined && sqlDPs[id][adapter.namespace].ignoreAboveNumber !== null && sqlDPs[id][adapter.namespace].ignoreAboveNumber !== '') { - sqlDPs[id][adapter.namespace].ignoreAboveNumber = parseFloat(sqlDPs[id][adapter.namespace].ignoreAboveNumber) || null; - } - - // ignoreBelowNumber and ignoreBelowZero compatibility handling - if (sqlDPs[id][adapter.namespace].ignoreBelowNumber !== undefined && sqlDPs[id][adapter.namespace].ignoreBelowNumber !== null && sqlDPs[id][adapter.namespace].ignoreBelowNumber !== '') { - sqlDPs[id][adapter.namespace].ignoreBelowNumber = parseFloat(sqlDPs[id][adapter.namespace].ignoreBelowNumber) || null; - } else if (sqlDPs[id][adapter.namespace].ignoreBelowZero === 'true' || sqlDPs[id][adapter.namespace].ignoreBelowZero === true) { - sqlDPs[id][adapter.namespace].ignoreBelowNumber = 0; - } - - // disableSkippedValueLogging - if (sqlDPs[id][adapter.namespace].disableSkippedValueLogging !== undefined && sqlDPs[id][adapter.namespace].disableSkippedValueLogging !== null && sqlDPs[id][adapter.namespace].disableSkippedValueLogging !== '') { - sqlDPs[id][adapter.namespace].disableSkippedValueLogging = sqlDPs[id][adapter.namespace].disableSkippedValueLogging === 'true' || sqlDPs[id][adapter.namespace].disableSkippedValueLogging === true; - } else { - sqlDPs[id][adapter.namespace].disableSkippedValueLogging = adapter.config.disableSkippedValueLogging; - } - - // enableDebugLogs - if (sqlDPs[id][adapter.namespace].enableDebugLogs !== undefined && sqlDPs[id][adapter.namespace].enableDebugLogs !== null && sqlDPs[id][adapter.namespace].enableDebugLogs !== '') { - sqlDPs[id][adapter.namespace].enableDebugLogs = sqlDPs[id][adapter.namespace].enableDebugLogs === 'true' || sqlDPs[id][adapter.namespace].enableDebugLogs === true; - } else { - sqlDPs[id][adapter.namespace].enableDebugLogs = adapter.config.enableDebugLogs; - } - - // changesRelogInterval - if (sqlDPs[id][adapter.namespace].changesRelogInterval !== undefined && sqlDPs[id][adapter.namespace].changesRelogInterval !== null && sqlDPs[id][adapter.namespace].changesRelogInterval !== '') { - sqlDPs[id][adapter.namespace].changesRelogInterval = parseInt(sqlDPs[id][adapter.namespace].changesRelogInterval, 10) || 0; - } else { - sqlDPs[id][adapter.namespace].changesRelogInterval = adapter.config.changesRelogInterval; - } - - // changesMinDelta - if (sqlDPs[id][adapter.namespace].changesMinDelta !== undefined && sqlDPs[id][adapter.namespace].changesMinDelta !== null && sqlDPs[id][adapter.namespace].changesMinDelta !== '') { - sqlDPs[id][adapter.namespace].changesMinDelta = parseFloat(sqlDPs[id][adapter.namespace].changesMinDelta.toString().replace(/,/g, '.')) || 0; - } else { - sqlDPs[id][adapter.namespace].changesMinDelta = adapter.config.changesMinDelta; - } - - // storageType - if (!sqlDPs[id][adapter.namespace].storageType) { - sqlDPs[id][adapter.namespace].storageType = false; - } - - // add one day if retention is too small - if (sqlDPs[id][adapter.namespace].retention && sqlDPs[id][adapter.namespace].retention <= 604800) { - sqlDPs[id][adapter.namespace].retention += 86400; - } - - // relogTimeout - if (sqlDPs[id][adapter.namespace] && sqlDPs[id][adapter.namespace].changesOnly && sqlDPs[id][adapter.namespace].changesRelogInterval > 0) { - sqlDPs[id].relogTimeout && clearTimeout(sqlDPs[id].relogTimeout); - sqlDPs[id].relogTimeout = setTimeout(reLogHelper, (sqlDPs[id][adapter.namespace].changesRelogInterval * 500 * Math.random()) + sqlDPs[id][adapter.namespace].changesRelogInterval * 500, id); - } - - sqlDPs[id].realId = realId; - sqlDPs[id].list = sqlDPs[id].list || []; - sqlDPs[id].inFlight = sqlDPs[id].inFlight || {}; - - // randomize lastCheck to avoid all datapoints to be checked at same timepoint - sqlDPs[id].lastCheck = Date.now() - Math.floor(Math.random() * 21600000/* 6 hours */); - } - } - } - } - - adapter.config.writeNulls && writeNulls(); - - if (count < 200) { - Object.keys(sqlDPs).forEach(id => - sqlDPs[id] && sqlDPs[id][adapter.namespace] && !sqlDPs[id].realId && adapter.log.warn(`No realID found for ${id}`)); - - // BF (2020.05.20) change it later to - // adapter.subscribeForeignStates(Object.keys(sqlDPs).forEach(id => sqlDPs[id] && sqlDPs[id][adapter.namespace] && sqlDPs[id].realId).filter(id => id)); - - Object.keys(sqlDPs).forEach(id => - sqlDPs[id] && sqlDPs[id][adapter.namespace] && sqlDPs[id].realId && adapter.subscribeForeignStates(sqlDPs[id].realId)); - } else { - subscribeAll = true; - adapter.subscribeForeignStates('*'); - } - adapter.subscribeForeignObjects('*'); - adapter.log.debug('Initialization done'); - setConnected(true); - processStartValues(); - - // store all buffered data every 10 minutes to not lost the data - bufferChecker = setInterval(() => storeCached(), 10 * 60000); - }); - }); - } -} - -// If started as allInOne/compact mode => return function to create instance -if (module.parent) { - module.exports = startAdapter; -} else { - // or start the instance directly - startAdapter(); -} diff --git a/package.json b/package.json index 0e92c57b..dbee2ceb 100644 --- a/package.json +++ b/package.json @@ -1,63 +1,70 @@ { - "name": "iobroker.sql", - "description": "Log states into SQL database", - "version": "3.0.1", - "author": "bluefox ", - "contributors": [ - "bluefox ", - "Apollon77" - ], - "homepage": "https://github.com/ioBroker/ioBroker.sql", - "repository": { - "type": "git", - "url": "https://github.com/ioBroker/ioBroker.sql" - }, - "engines": { - "node": ">=16.0.0" - }, - "keywords": [ - "ioBroker", - "log data", - "home automation" - ], - "optionalDependencies": { - "mysql2": "^3.6.5", - "pg": "^8.11.3", - "sqlite3": "^5.1.6", - "mssql": "^10.0.1" - }, - "dependencies": { - "sql-client": "^3.0.0", - "@iobroker/adapter-core": "^3.1.6" - }, - "devDependencies": { - "@alcalzone/release-script": "^3.7.0", - "@alcalzone/release-script-plugin-iobroker": "^3.7.0", - "@alcalzone/release-script-plugin-license": "^3.7.0", - "axios": "^1.12.2", - "gulp": "^4.0.2", - "mocha": "^10.8.2", - "chai": "^4.3.8" - }, - "bugs": { - "url": "https://github.com/ioBroker/ioBroker.sql/issues" - }, - "main": "main.js", - "files": [ - "admin/", - "img/", - "lib/", - "io-package.json", - "LICENSE", - "main.js" - ], - "scripts": { - "test": "node node_modules/mocha/bin/mocha --exit", - "release": "release-script", - "release-patch": "release-script patch --yes", - "release-minor": "release-script minor --yes", - "release-major": "release-script major --yes", - "update-packages": "ncu --upgrade" - }, - "license": "MIT" + "name": "iobroker.sql", + "description": "Log states into SQL database", + "version": "3.0.1", + "author": "bluefox ", + "contributors": [ + "bluefox ", + "Apollon77" + ], + "homepage": "https://github.com/ioBroker/ioBroker.sql", + "repository": { + "type": "git", + "url": "https://github.com/ioBroker/ioBroker.sql" + }, + "engines": { + "node": ">=16.0.0" + }, + "keywords": [ + "ioBroker", + "log data", + "home automation" + ], + "optionalDependencies": { + "mssql": "^11.0.1", + "mysql2": "^3.15.2", + "pg": "^8.16.3", + "sqlite3": "^5.1.7" + }, + "dependencies": { + "@iobroker/adapter-core": "^3.3.2", + "dockerode": "^4.0.9", + "@iobroker/plugin-docker": "^0.1.5" + }, + "devDependencies": { + "@iobroker/eslint-config": "^2.2.0", + "@types/mssql": "^9.1.8", + "@types/pg": "^8.15.5", + "@types/sqlite3": "^5.1.0", + "@types/tar-fs": "^2.0.4", + "@alcalzone/release-script": "^4.0.0", + "@alcalzone/release-script-plugin-iobroker": "^4.0.0", + "@alcalzone/release-script-plugin-license": "^4.0.0", + "axios": "^1.12.2", + "chai": "^4.5.0", + "mocha": "^11.7.4" + }, + "bugs": { + "url": "https://github.com/ioBroker/ioBroker.sql/issues" + }, + "main": "build/main.js", + "files": [ + "admin/", + "img/", + "build/", + "io-package.json", + "LICENSE" + ], + "scripts": { + "test": "node node_modules/mocha/bin/mocha --exit", + "lint": "eslint -c eslint.config.mjs", + "build": "tsc -p tsconfig.build.json", + "prepublishOnly": "npm run build", + "release": "release-script", + "release-patch": "release-script patch --yes", + "release-minor": "release-script minor --yes", + "release-major": "release-script major --yes", + "update-packages": "ncu --upgrade" + }, + "license": "MIT" } diff --git a/prettier.config.mjs b/prettier.config.mjs new file mode 100644 index 00000000..adce4487 --- /dev/null +++ b/prettier.config.mjs @@ -0,0 +1,8 @@ +// iobroker prettier configuration file +import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs'; + +export default { + ...prettierConfig, + // uncomment next line if you prefer double quotes + // singleQuote: false, +}; diff --git a/src/lib/DockerManager.ts b/src/lib/DockerManager.ts new file mode 100644 index 00000000..a3bb59e1 --- /dev/null +++ b/src/lib/DockerManager.ts @@ -0,0 +1,2521 @@ +// This class implements docker commands using CLI and +// it monitors periodically the docker daemon status. +// It manages containers defined in adapter.config.containers and monitors other containers + +import { promisify } from 'node:util'; +import { exec } from 'node:child_process'; +import type { + ContainerConfig, + ContainerInfo, + ContainerStats, + ContainerStatus, + DiskUsage, + DockerContainerInspect, + DockerImageInspect, + ImageInfo, + ContainerName, + ImageName, + NetworkInfo, + NetworkDriver, + VolumeInfo, + VolumeDriver, +} from './dockerManager.types'; +import { createConnection } from 'node:net'; +import Docker, { type MountPropagation, type MountSettings, type MountType } from 'dockerode'; +import type { PackOptions, Pack } from 'tar-fs'; + +const execPromise = promisify(exec); + +const dockerDefaults: Record = { + tty: false, + stdinOpen: false, + attachStdin: false, + attachStdout: false, + attachStderr: false, + openStdin: false, + publishAllPorts: false, + readOnly: false, + user: '', + workdir: '', + domainname: '', + macAddress: '', + networkMode: 'bridge', +}; + +function isDefault(value: any, def: any): boolean { + return JSON.stringify(value) === JSON.stringify(def); +} + +function deepCompare(object1: any, object2: any): boolean { + if (typeof object1 === 'number') { + object1 = object1.toString(); + } + if (typeof object2 === 'number') { + object2 = object2.toString(); + } + if (typeof object1 !== typeof object2) { + return false; + } + if (typeof object1 !== 'object' || object1 === null || object2 === null) { + return object1 === object2; + } + if (Array.isArray(object1)) { + if (!Array.isArray(object2) || object1.length !== object2.length) { + return false; + } + for (let i = 0; i < object1.length; i++) { + if (!deepCompare(object1[i], object2[i])) { + return false; + } + } + return true; + } + const keys1 = Object.keys(object1); + for (const key of keys1) { + // ignore iob* properties as they belong to ioBroker configuration + // ignore hostname + if (key.startsWith('iob') || key === 'hostname') { + continue; + } + if (!deepCompare(object1[key], object2[key])) { + return false; + } + } + return true; +} + +function compareConfigs(desired: ContainerConfig, existing: ContainerConfig): string[] { + const diffs: string[] = []; + + const keys: (keyof ContainerConfig)[] = Object.keys(desired) as Array; + + // We only compare keys that are in the desired config + for (const key of keys) { + // ignore iob* properties as they belong to ioBroker configuration + // ignore hostname + if (key.startsWith('iob') || key === 'hostname') { + continue; + } + if (typeof desired[key] === 'object' && desired[key] !== null) { + if (Array.isArray(desired[key])) { + if (!Array.isArray(existing[key]) || desired[key].length !== existing[key].length) { + diffs.push(key); + } else { + for (let i = 0; i < desired[key].length; i++) { + if (!deepCompare(desired[key][i], existing[key][i])) { + diffs.push(`${key}[${i}]`); + } + } + } + } else { + Object.keys(desired[key]).forEach((subKey: string) => { + if (!deepCompare((desired as any)[key][subKey], (existing as any)[key][subKey])) { + diffs.push(`${key}.${subKey}`); + } + }); + } + } else if (desired[key] !== existing[key]) { + diffs.push(key); + } + } + + return diffs; +} + +// remove undefined entries recursively +function removeUndefined(obj: any): any { + if (Array.isArray(obj)) { + const arr = obj.map(v => (v && typeof v === 'object' ? removeUndefined(v) : v)).filter(v => v !== undefined); + if (!arr.length) { + return undefined; + } + return arr; + } + if (obj && typeof obj === 'object') { + const _obj = Object.fromEntries( + Object.entries(obj) + .map(([k, v]) => [k, v && typeof v === 'object' ? removeUndefined(v) : v]) + .filter( + ([_, v]) => + v !== undefined && + v !== null && + v !== '' && + !(Array.isArray(v) && v.length === 0) && + !(typeof v === 'object' && Object.keys(v).length === 0), + ), + ); + if (Object.keys(_obj).length === 0) { + return undefined; + } + return _obj; + } + if (obj === '') { + return undefined; + } + return obj; +} + +function cleanContainerConfig(obj: ContainerConfig, mayChange?: boolean): ContainerConfig { + obj = removeUndefined(obj); + + Object.keys(obj).forEach(name => { + if (isDefault((obj as any)[name], (dockerDefaults as any)[name])) { + delete (obj as any)[name]; + } + if (name === 'mounts') { + if (!obj.mounts) { + delete obj.mounts; + return; + } + obj.mounts = obj.mounts.map((mount: any) => { + const m = { ...mount }; + // /var/lib/docker/volumes/influxdb_0_flux_config/_data + if (mayChange && m.source.includes('/docker/volumes') && m.source.endsWith('/_data')) { + const parts = m.source.split('/'); + m.source = parts[parts.length - 2]; + } + delete m.readOnly; + return m; + }); + if (!obj.mounts.length) { + delete obj.mounts; + return; + } + obj.mounts?.sort((a, b) => a.target.localeCompare(b.target)); + } + if (name === 'ports') { + if (!obj.ports) { + delete obj.ports; + return; + } + obj.ports = obj.ports.map((port: any) => { + const p = { ...port }; + if (p.protocol === 'tcp') { + delete p.protocol; + } + return p; + }); + if (!obj.ports.length) { + delete obj.ports; + return; + } + obj.ports?.sort((a, b) => { + if (a.hostPort !== b.hostPort) { + return parseInt(a.containerPort as string, 10) - parseInt(b.containerPort as string, 10); + } + if (a.hostIP !== b.hostIP && a.hostIP && b.hostIP) { + return a.hostIP?.localeCompare(b.hostIP); + } + return 0; + }); + } + if (name === 'environment') { + if (!obj.environment) { + delete obj.environment; + return; + } + const env = obj.environment as { [key: string]: string }; + if (Object.keys(env).length) { + obj.environment = {}; + Object.keys(env) + .sort() + .forEach(key => { + if (key && env[key]) { + obj.environment![key] = env[key]; + } + }); + } else { + delete obj.environment; + } + if (!Object.keys(env).length) { + delete obj.environment; + } + } + if (name === 'labels') { + if (!obj.labels) { + delete obj.labels; + return; + } + const labels = obj.labels as { [key: string]: string }; + if (Object.keys(labels).length) { + obj.labels = {}; + Object.keys(labels) + .sort() + .forEach(key => { + if (key && labels[key]) { + obj.labels![key] = labels[key]; + } + }); + } else { + delete obj.labels; + } + if (!Object.keys(labels).length) { + delete obj.labels; + } + } + if (name === 'volumes') { + if (!obj.volumes?.length) { + delete obj.volumes; + return; + } + obj.volumes = obj.volumes.map(v => v.trim()).filter(v => v); + obj.volumes.sort(); + if (!obj.volumes?.length) { + delete obj.volumes; + } + } + }); + + obj.volumes?.sort(); + return obj; +} + +function size2string(size: number): string { + if (size < 1024) { + return `${size} B`; + } + if (size < 1024 * 1024) { + return `${(size / 1024).toFixed(2)} KB`; + } + if (size < 1024 * 1024 * 1024) { + return `${(size / (1024 * 1024)).toFixed(2)} MB`; + } + return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`; +} + +export default class DockerManager { + protected installed: boolean = false; + protected dockerVersion: string = ''; + protected needSudo: boolean = false; + readonly #waitReady: Promise; + readonly #waitAllChecked: Promise; + #waitAllCheckedResolve: (() => void) | undefined; + readonly adapter: ioBroker.Adapter; + readonly #ownContainers: ContainerConfig[] = []; + #monitoringInterval: NodeJS.Timeout | null = null; + #ownContainersStats: { [name: string]: ContainerStatus } = {}; + #driver: 'socket' | 'cli' | 'http' | 'https' = 'cli'; + #dockerode: Docker | null = null; + #cliAvailable: boolean = false; + protected options: { + dockerApi?: boolean; + dockerApiHost?: string; + dockerApiPort?: number | string; + dockerApiProtocol?: 'http' | 'https'; + }; + #tarPack: ((cwd: string, opts?: PackOptions) => Pack) | null = null; + + constructor( + adapter: ioBroker.Adapter, + options?: { + dockerApi?: boolean; + dockerApiHost?: string; + dockerApiPort?: number | string; + dockerApiProtocol?: 'http' | 'https'; + }, + containers?: ContainerConfig[], + ) { + this.adapter = adapter; + this.options = options || {}; + this.#ownContainers = containers || []; + this.#waitReady = new Promise(resolve => this.#init().then(() => resolve())); + this.#waitAllChecked = new Promise(resolve => (this.#waitAllCheckedResolve = resolve)); + } + + /** Wait till the check if docker is installed and the daemon is running is ready */ + isReady(): Promise { + return this.#waitReady; + } + + /** + * Convert information from inspect to docker configuration to start it + * + * @param inspect Inspect information + */ + static mapInspectToConfig(inspect: DockerContainerInspect): ContainerConfig { + const obj: ContainerConfig = { + image: inspect.Config.Image, + name: inspect.Name.replace(/^\//, ''), + command: inspect.Config.Cmd ?? undefined, + entrypoint: inspect.Config.Entrypoint ?? undefined, + user: inspect.Config.User ?? undefined, + workdir: inspect.Config.WorkingDir ?? undefined, + hostname: inspect.Config.Hostname ?? undefined, + domainname: inspect.Config.Domainname ?? undefined, + macAddress: inspect.NetworkSettings.MacAddress ?? undefined, + environment: inspect.Config.Env + ? Object.fromEntries( + inspect.Config.Env.map(e => { + const [key, ...rest] = e.split('='); + return [key, rest.join('=')]; + }), + ) + : undefined, + labels: inspect.Config.Labels ?? undefined, + tty: inspect.Config.Tty, + stdinOpen: inspect.Config.OpenStdin, + attachStdin: inspect.Config.AttachStdin, + attachStdout: inspect.Config.AttachStdout, + attachStderr: inspect.Config.AttachStderr, + openStdin: inspect.Config.OpenStdin, + publishAllPorts: inspect.HostConfig.PublishAllPorts, + ports: inspect.HostConfig.PortBindings + ? Object.entries(inspect.HostConfig.PortBindings).flatMap(([containerPort, bindings]) => + bindings.map(binding => ({ + containerPort: containerPort.split('/')[0], + protocol: (containerPort.split('/')[1] as 'tcp' | 'udp') || 'tcp', + hostPort: binding.HostPort, + hostIP: binding.HostIp, + })), + ) + : undefined, + mounts: inspect.Mounts?.map(mount => ({ + type: mount.Type, + source: mount.Source, + target: mount.Destination, + readOnly: mount.RW, + })), + volumes: inspect.Config.Volumes ? Object.keys(inspect.Config.Volumes) : undefined, + extraHosts: inspect.HostConfig.ExtraHosts ?? undefined, + dns: { + servers: inspect.HostConfig.Dns, + search: inspect.HostConfig.DnsSearch, + options: inspect.HostConfig.DnsOptions, + }, + networkMode: inspect.HostConfig.NetworkMode, + networks: inspect.NetworkSettings.Networks + ? Object.entries(inspect.NetworkSettings.Networks).map(([name, net]) => ({ + name, + aliases: net.Aliases ?? undefined, + ipv4Address: net.IPAddress, + ipv6Address: net.GlobalIPv6Address, + driverOpts: net.DriverOpts ?? undefined, + })) + : undefined, + restart: { + policy: inspect.HostConfig.RestartPolicy.Name as any, + maxRetries: inspect.HostConfig.RestartPolicy.MaximumRetryCount, + }, + resources: { + cpuShares: inspect.HostConfig.CpuShares, + cpuQuota: inspect.HostConfig.CpuQuota, + cpuPeriod: inspect.HostConfig.CpuPeriod, + cpusetCpus: inspect.HostConfig.CpusetCpus, + memory: inspect.HostConfig.Memory, + memorySwap: inspect.HostConfig.MemorySwap, + memoryReservation: inspect.HostConfig.MemoryReservation, + pidsLimit: inspect.HostConfig.PidsLimit ?? undefined, + shmSize: inspect.HostConfig.ShmSize, + readOnlyRootFilesystem: inspect.HostConfig.ReadonlyRootfs, + }, + logging: { + driver: inspect.HostConfig.LogConfig.Type, + options: inspect.HostConfig.LogConfig.Config, + }, + security: { + privileged: inspect.HostConfig.Privileged, + capAdd: inspect.HostConfig.CapAdd ?? undefined, + capDrop: inspect.HostConfig.CapDrop ?? undefined, + usernsMode: inspect.HostConfig.UsernsMode ?? undefined, + ipc: inspect.HostConfig.IpcMode, + pid: inspect.HostConfig.PidMode, + seccomp: + inspect.HostConfig.SecurityOpt?.find(opt => opt.startsWith('seccomp='))?.split('=')[1] ?? undefined, + apparmor: + inspect.HostConfig.SecurityOpt?.find(opt => opt.startsWith('apparmor='))?.split('=')[1] ?? + undefined, + groupAdd: inspect.HostConfig.GroupAdd ?? undefined, + noNewPrivileges: undefined, // Nicht direkt verfügbar + }, + sysctls: inspect.HostConfig.Sysctls ?? undefined, + init: inspect.HostConfig.Init ?? undefined, + stop: { + signal: inspect.Config.StopSignal ?? undefined, + gracePeriodSec: inspect.Config.StopTimeout ?? undefined, + }, + readOnly: inspect.HostConfig.ReadonlyRootfs, + timezone: undefined, // Nicht direkt verfügbar + __meta: undefined, // Eigene Metadaten + }; + + return cleanContainerConfig(obj, true); + } + + /** + * Get information about the Docker daemon: is it running and which version + * + * @returns Object with version and daemonRunning + */ + async getDockerDaemonInfo(): Promise<{ + version?: string; + daemonRunning?: boolean; + removeSupported?: boolean; + driver: 'socket' | 'cli' | 'http' | 'https'; + }> { + await this.isReady(); + const daemonRunning = await this.#isDockerDaemonRunning(); + return { + version: this.dockerVersion, + daemonRunning, + removeSupported: !this.#dockerode || this.#cliAvailable, + driver: this.#driver, + }; + } + + static checkDockerSocket(): Promise { + return new Promise(resolve => { + const socket = createConnection({ path: '/var/run/docker.sock' }, () => { + socket.end(); + resolve(true); + }); + socket.on('error', e => { + console.error(`Cannot connect to docker socket: ${e.message}`); + resolve(false); + }); + }); + } + + static async isDockerApiRunningOnPort(port: number, host = 'localhost'): Promise { + return new Promise(resolve => { + const socket = createConnection({ port, host }, () => { + socket.write('GET /version HTTP/1.0\r\nHost: localhost\r\n\r\n'); + }); + + let data = ''; + socket.on('data', chunk => (data += chunk.toString())); + + socket.on('end', () => { + resolve(data.includes('Docker') || data.includes('Api-Version')); + }); + + socket.on('error', () => resolve(false)); + }); + } + + async #init(): Promise { + // first of all detects which way is available: + // - '/var/run/docker.sock', + // - http://localhost:2375, + // - https://localhost:2376 or + // - CLI + // Probe the socket + if (this.options?.dockerApi && this.options.dockerApiHost && this.options.dockerApiPort) { + this.#driver = this.options.dockerApiProtocol || 'http'; + this.#dockerode = new Docker({ + host: this.options.dockerApiHost, + port: this.options.dockerApiPort, + protocol: this.options.dockerApiProtocol || 'http', + }); + } else if (await DockerManager.checkDockerSocket()) { + this.#driver = 'socket'; + this.#dockerode = new Docker({ socketPath: '/var/run/docker.sock' }); + } else if (await DockerManager.isDockerApiRunningOnPort(2375)) { + this.#driver = 'http'; + this.#dockerode = new Docker({ protocol: 'http', host: '127.0.0.1', port: 2375 }); + } else if (await DockerManager.isDockerApiRunningOnPort(2376)) { + this.#driver = 'http'; + this.#dockerode = new Docker({ protocol: 'http', host: '127.0.0.1', port: 2376 }); + } else { + this.#driver = 'cli'; + this.#cliAvailable = true; + } + + if (!this.#cliAvailable) { + try { + const result = await execPromise('docker --version'); + if (!result.stderr && result.stdout) { + this.#cliAvailable = true; + } + } catch { + // ignore + } + } + + const version = await this.#isDockerInstalled(); + this.installed = !!version; + if (version) { + this.dockerVersion = version; + } else { + const daemonRunning = await this.#isDockerDaemonRunning(); + if (daemonRunning) { + // Docker daemon is running, but docker command not found + this.adapter.log.warn( + 'Docker daemon is running, but docker command not found. May be "iobroker" user has no access to Docker. Run "iob fix" command to fix it.', + ); + } else { + this.adapter.log.warn('Docker is not installed. Please install Docker.'); + } + } + if (this.installed) { + // we still must check the sudo as autocompletion works only via CLI + this.needSudo = await this.#isNeedSudo(); + await this.#checkOwnContainers(); + } else { + this.#waitAllCheckedResolve?.(); + } + } + + async #isDockerDaemonRunning(): Promise { + if (this.#dockerode) { + return true; + } + try { + const { stdout, stderr } = await execPromise('systemctl status docker'); + // ● docker.service - Docker Application Container Engine + // Loaded: loaded (/lib/systemd/system/docker.service; enabled; preset: enabled) + // Active: active (running) since Fri 2025-08-15 08:37:22 CEST; 3 weeks 2 days ago + // TriggeredBy: ● docker.socket + // Docs: https://docs.docker.com + // Main PID: 785 (dockerd) + // Tasks: 30 + // CPU: 4min 17.003s + // CGroup: /system.slice/docker.service + // ├─ 785 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock + // ├─97032 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 5000 -container-ip 172.17.0.2 -container-port 5000 -use-listen-fd + // └─97039 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 5000 -container-ip 172.17.0.2 -container-port 5000 -use-listen-fd + if (stderr?.includes('could not be found') || stderr.includes('not-found')) { + this.adapter.log.error(`Docker is not installed: ${stderr}`); + return false; + } + + return stdout.includes('(running)'); + } catch { + return false; + } + } + + /** + * Ensure that the given container is running with the actual configuration + * + * @param container Container configuration + */ + async #ensureActualConfiguration(container: ContainerConfig): Promise { + if (!container.name) { + throw new Error(`Container name must be a string, but got boolean true`); + } + // Check the configuration of the container + const inspect = await this.containerInspect(container.name); + if (inspect) { + const existingConfig = DockerManager.mapInspectToConfig(inspect); + console.log('Compare existing config', existingConfig, ' and', container); + container = cleanContainerConfig(container); + const diffs = compareConfigs(container, existingConfig); + if (diffs.length) { + this.adapter.log.info( + `Configuration of own container ${container.name} has changed: ${diffs.join( + ', ', + )}. Restarting container...`, + ); + const result = await this.containerReCreate(container); + if (result.stderr) { + this.adapter.log.warn(`Cannot recreate own container ${container.name}: ${result.stderr}`); + } + } else { + this.adapter.log.debug(`Configuration of own container ${container.name} is up to date`); + } + + // Check if container is running + const status = await this.containerList(true); + const containerInfo = status.find(it => it.names === container.name); + if (containerInfo) { + if (containerInfo.status !== 'running' && containerInfo.status !== 'restarting') { + // Start the container + this.adapter.log.info(`Starting own container ${container.name}`); + try { + const result = await this.containerStart(containerInfo.id); + if (result.stderr) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${result.stderr}`); + } + } catch (e) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${e.message}`); + } + } else { + this.adapter.log.debug(`Own container ${container.name} is already running`); + } + } else { + this.adapter.log.warn(`Own container ${container.name} not found in container list after recreation`); + } + } + } + + getDefaultContainerName(): string { + return `iob_${this.adapter.namespace.replace(/[-.]/g, '_')}`; + } + + async #checkOwnContainers(): Promise { + if (!this.#ownContainers.length) { + this.#waitAllCheckedResolve?.(); + return; + } + const status = await this.containerList(true); + let images = await this.imageList(); + let anyStartedOrRunning = false; + const networkChecked: string[] = []; + const prefix = this.getDefaultContainerName(); + for (let c = 0; c < this.#ownContainers.length; c++) { + const container = this.#ownContainers[c]; + if (container.iobEnabled !== false) { + if (!container.image.includes(':')) { + container.image += ':latest'; + } + if (container.labels?.iobroker !== this.adapter.namespace) { + container.labels = { ...container.labels, iobroker: this.adapter.namespace }; + } + container.name ||= prefix; + + // Name of the container, name of the network and name of the volume must start with iob___ + if (container.name !== prefix && !container.name.startsWith(`${prefix}_`)) { + this.adapter.log.debug(`Renaming container ${container.name} to be prefixed with iob_${prefix}_`); + container.name = `${prefix}_${container.name}`; + } + + try { + // create iobroker network if necessary + if ( + container.networkMode && + container.networkMode !== 'container' && + container.networkMode !== 'host' && + container.networkMode !== 'bridge' && + container.networkMode !== 'none' + ) { + if (container.networkMode === true) { + container.networkMode = prefix; + } + if (container.networkMode !== prefix && !container.networkMode.startsWith(`${prefix}_`)) { + this.adapter.log.debug( + `Renaming network ${container.networkMode} to be prefixed with ${prefix}_`, + ); + container.networkMode = `${prefix}_${container.networkMode}`; + } + + if (!networkChecked.includes(container.networkMode)) { + // check if the network exists + const networks = await this.networkList(); + if (!networks.find(it => it.name === container.networkMode)) { + this.adapter.log.info(`Creating docker network ${container.networkMode}`); + await this.networkCreate(container.networkMode); + } + + networkChecked.push(container.networkMode); + } + } + + // create all volumes ourselves, to have a static name + if (container.mounts?.find(m => m.type === 'volume')) { + // check if the volume exists + const volumes = await this.volumeList(); + for (const mount of container.mounts) { + if (mount.type === 'volume' && mount.source) { + if (mount.source === true) { + mount.source = prefix; + } + + if (mount.source !== prefix && !mount.source.startsWith(`${prefix}_`)) { + this.adapter.log.debug( + `Renaming volume ${mount.source} to be prefixed with ${prefix}_`, + ); + mount.source = `${prefix}_${mount.source}`; + } + if (mount.iobBackup) { + if (!container.labels.iob_backup) { + container.labels = { ...container.labels, iob_backup: mount.source }; + } else { + const volumes: string[] = container.labels.iob_backup + .split(',') + .map(v => v.trim()) + .filter(v => v); + if (!volumes.includes(mount.source)) { + volumes.push(mount.source); + container.labels = { ...container.labels, iob_backup: volumes.join(',') }; + } + } + } + + const volume = volumes.find(v => v.name === mount.source); + if (!volume) { + this.adapter.log.info(`Creating docker volume ${mount.source}`); + const result = await this.volumeCreate(mount.source); + if (result.stderr) { + this.adapter.log.warn(`Cannot create volume ${mount.source}: ${result.stderr}`); + continue; + } + // Copy data from host to volume + if (mount.iobAutoCopyFrom) { + await this.volumeCopyTo(mount.source, mount.iobAutoCopyFrom); + } + } else if (mount.iobAutoCopyFromForce && mount.iobAutoCopyFrom) { + // Copy data from host to volume + await this.volumeCopyTo(mount.source, mount.iobAutoCopyFrom); + } + } + } + } + + let containerInfo = status.find(it => it.names === container.name); + let image = images.find(it => `${it.repository}:${it.tag}` === container.image); + if (container.iobAutoImageUpdate) { + // ensure that the image is actual + const newImage = await this.imageUpdate(container.image, true); + if (newImage) { + this.adapter.log.info( + `Image ${container.image} for own container ${container.name} was updated`, + ); + if (containerInfo) { + // destroy current container + await this.containerRemove(containerInfo.id); + containerInfo = undefined; + } + image = newImage; + } + } + if (!image) { + this.adapter.log.info(`Pulling image ${container.image} for own container ${container.name}`); + try { + const result = await this.imagePull(container.image); + if (result.stderr) { + this.adapter.log.warn(`Cannot pull image ${container.image}: ${result.stderr}`); + continue; + } + } catch (e) { + this.adapter.log.warn(`Cannot pull image ${container.image}: ${e.message}`); + continue; + } + // Check that image is available now + images = await this.imageList(); + image = images.find(it => `${it.repository}:${it.tag}` === container.image); + if (!image) { + this.adapter.log.warn( + `Image ${container.image} for own container ${container.name} not found after pull`, + ); + continue; + } + } + + if (containerInfo) { + await this.#ensureActualConfiguration(container); + anyStartedOrRunning ||= !!container.iobMonitoringEnabled; + } else { + // Create and start the container, as the container was not found + this.adapter.log.info(`Creating and starting own container ${container.name}`); + + try { + const result = await this.containerRun(container); + if (result.stderr) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${result.stderr}`); + } else { + anyStartedOrRunning ||= !!container.iobMonitoringEnabled; + } + } catch (e) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${e.message}`); + } + } + } catch (e) { + this.adapter.log.warn(`Cannot check own container ${container.name}: ${e.message}`); + } + } + } + + if (anyStartedOrRunning) { + this.#monitoringInterval ||= setInterval(() => this.#monitorOwnContainers(), 60000); + } + this.#waitAllCheckedResolve?.(); + } + + allOwnContainersChecked(): Promise { + return this.#waitAllChecked; + } + + async #monitorOwnContainers(): Promise { + // get the status of containers + const containers = await this.containerList(); + // Check the status of own containers + for (let c = 0; c < this.#ownContainers.length; c++) { + const container = this.#ownContainers[c]; + if (container.iobEnabled !== false && container.iobMonitoringEnabled && container.name) { + // Check if container is running + const running = containers.find(it => it.names === container.name); + if (!running || (running.status !== 'running' && running.status !== 'restarting')) { + this.adapter.log.warn(`Own container ${container.name} is not running. Restarting...`); + try { + const result = await this.containerStart(container.name); + if (result.stderr) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${result.stderr}`); + this.#ownContainersStats[container.name] = { + ...this.#ownContainersStats[container.name], + status: running?.status || 'unknown', + statusTs: Date.now(), + }; + continue; + } + } catch (e) { + this.adapter.log.warn(`Cannot start own container ${container.name}: ${e.message}`); + this.#ownContainersStats[container.name] = { + ...this.#ownContainersStats[container.name], + status: running?.status || 'unknown', + statusTs: Date.now(), + }; + continue; + } + } + + // check the stats + this.#ownContainersStats[container.name] = { + ...((await this.containerGetRamAndCpuUsage(container.name)) || ({} as ContainerStats)), + status: running?.status || 'unknown', + statusTs: Date.now(), + }; + } + } + } + + /** Read own container stats */ + getOwnContainerStats(): { [name: string]: ContainerStatus } { + return this.#ownContainersStats; + } + + async containerGetRamAndCpuUsage(containerNameOrId: ContainerName): Promise { + try { + const { stdout } = await this.#exec( + `stats ${containerNameOrId} --no-stream --format "{{.CPUPerc}};{{.MemUsage}};{{.NetIO}};{{.BlockIO}};{{.PIDs}}"`, + ); + // Example: "0.15%;12.34MiB / 512MiB;1.2kB / 2.3kB;0B / 0B;5" + const [cpuStr, memStr, netStr, blockIoStr, pid] = stdout.trim().split(';'); + const [memUsed, memMax] = memStr.split('/').map(it => it.trim()); + const [netRead, netWrite] = netStr.split('/').map(it => it.trim()); + const [blockIoRead, blockIoWrite] = blockIoStr.split('/').map(it => it.trim()); + + return { + ts: Date.now(), + cpu: parseFloat(cpuStr.replace('%', '').replace(',', '.')), + memUsed: this.#parseSize(memUsed.replace('iB', 'B')), + memMax: this.#parseSize(memMax.replace('iB', 'B')), + netRead: this.#parseSize(netRead.replace('iB', 'B')), + netWrite: this.#parseSize(netWrite.replace('iB', 'B')), + processes: parseInt(pid, 10), + blockIoRead: this.#parseSize(blockIoRead.replace('iB', 'B')), + blockIoWrite: this.#parseSize(blockIoWrite.replace('iB', 'B')), + }; + } catch (e) { + this.adapter.log.debug(`Cannot get stats: ${e.message}`); + return null; + } + } + + /** + * Update the image if a newer version is available + * + * @param image Image name with tag + * @param ignoreIfNotExist If true, do not throw error if image does not exist + * @returns New image info if image was updated, null if no update was necessary + */ + async imageUpdate(image: ImageName, ignoreIfNotExist?: boolean): Promise { + const list = await this.imageList(); + if (!image.includes(':')) { + image += ':latest'; + } + const existingImage = list.find(it => `${it.repository}:${it.tag}` === image); + if (!existingImage && !ignoreIfNotExist) { + throw new Error(`Image ${image} not found`); + } + // Pull the image + const result = await this.imagePull(image); + if (result.stderr) { + throw new Error(`Cannot pull image ${image}: ${result.stderr}`); + } + const newList = await this.imageList(); + const newImage = newList.find(it => `${it.repository}:${it.tag}` === image); + if (!newImage) { + throw new Error(`Image ${image} not found after pull`); + } + // If image ID has changed, image was updated + return !existingImage || existingImage.id !== newImage.id ? newImage : null; + } + + #exec(command: string): Promise<{ stdout: string; stderr: string }> { + if (!this.installed) { + return Promise.reject(new Error('Docker is not installed')); + } + const finalCommand = this.needSudo ? `sudo docker ${command}` : `docker ${command}`; + return execPromise(finalCommand); + } + + async #isDockerInstalled(): Promise { + if (this.#driver === 'cli') { + try { + const result = await execPromise('docker --version'); + if (!result.stderr && result.stdout) { + // "Docker version 28.3.2, build 578ccf6\n" + return result.stdout.split('\n')[0].trim(); + } + this.adapter.log.debug(`Docker not installed: ${result.stderr}`); + } catch (e) { + this.adapter.log.debug(`Docker not installed: ${e.message}`); + } + } else if (this.#dockerode) { + try { + const info = await this.#dockerode.version(); + if (info?.Version) { + return `Docker version ${info.Version}, api version: ${info.ApiVersion}`; + } + } catch (e) { + this.adapter.log.debug(`Docker not installed: ${e.message}`); + } + } + return false; + } + + async #isNeedSudo(): Promise { + try { + await execPromise('docker ps'); + return false; + } catch { + return true; + } + } + + /** Get disk usage information */ + async discUsage(): Promise { + if (this.#dockerode) { + const info = await this.#dockerode.df(); + const result: DiskUsage = { total: { size: 0, reclaimable: 0 } }; + + if (info.Images) { + let size = 0; + let reclaimable = 0; + for (const image of info.Images) { + size += image.Size; + reclaimable += image.SharedSize + image.VirtualSize; + } + result.images = { + total: info.Images.length, + // @ts-expect-error todo + active: info.Images.filter(img => img.Containers > 0).length, + size, + reclaimable, + }; + result.total.size += size; + result.total.reclaimable += reclaimable; + } + + if (info.Containers) { + let size = 0; + for (const container of info.Containers) { + size += container.SizeRootFs || 0; + } + result.containers = { + total: info.Containers.length, + // @ts-expect-error todo + active: info.Containers.filter(cont => cont.State === 'running').length, + size, + reclaimable: 0, // Not available + }; + result.total.size += size; + } + + if (info.Volumes) { + let size = 0; + for (const volume of info.Volumes) { + size += volume.UsageData?.Size || 0; + } + result.volumes = { + total: info.Volumes.length, + // @ts-expect-error todo + active: info.Volumes.filter(vol => vol.UsageData?.RefCount && vol.UsageData.RefCount > 0).length, + size, + reclaimable: 0, // Not available + }; + result.total.size += size; + } + + // Build cache not available + + return result; + } + const { stdout } = await this.#exec(`system df`); + const result: DiskUsage = { total: { size: 0, reclaimable: 0 } }; + // parse the output + // TYPE TOTAL ACTIVE SIZE RECLAIMABLE + // Images 2 1 2.715GB 2.715GB (99%) + // Containers 1 1 26.22MB 0B (0%) + // Local Volumes 0 0 0B 0B + // Build Cache 0 0 0B 0B + const lines = stdout.split('\n'); + for (const line of lines) { + const parts = line.trim().split(/\s+/); + if (parts.length >= 5 && parts[0] !== 'TYPE') { + let size: number | undefined; + let reclaimable: number | undefined; + + if (parts[0] === 'Images') { + const sizeStr = parts[3]; + const reclaimableStr = parts[4].split(' ')[0]; + size = this.#parseSize(sizeStr); + reclaimable = this.#parseSize(reclaimableStr); + result.images = { + total: parseInt(parts[1], 10), + active: parseInt(parts[2], 10), + size, + reclaimable: reclaimable, + }; + } else if (parts[0] === 'Containers') { + const sizeStr = parts[3]; + const reclaimableStr = parts[4].split(' ')[0]; + size = this.#parseSize(sizeStr); + reclaimable = this.#parseSize(reclaimableStr); + result.containers = { + total: parseInt(parts[1], 10), + active: parseInt(parts[2], 10), + size, + reclaimable: reclaimable, + }; + } else if (parts[0] === 'Local' && parts[1] === 'Volumes') { + const sizeStr = parts[4]; + const reclaimableStr = parts[5].split(' ')[0]; + size = this.#parseSize(sizeStr); + reclaimable = this.#parseSize(reclaimableStr); + result.volumes = { + total: parseInt(parts[2], 10), + active: parseInt(parts[3], 10), + size, + reclaimable: reclaimable, + }; + } else if (parts[0] === 'Build' && parts[1] === 'Cache') { + const sizeStr = parts[4]; + const reclaimableStr = parts[5].split(' ')[0]; + size = this.#parseSize(sizeStr); + reclaimable = this.#parseSize(reclaimableStr); + result.buildCache = { + total: parseInt(parts[2], 10), + active: parseInt(parts[3], 10), + size, + reclaimable: reclaimable, + }; + } + result.total.size += size || 0; + result.total.reclaimable += reclaimable || 0; + } + } + return result; + } + + /** Pull an image from the registry */ + async imagePull(image: ImageName): Promise<{ stdout: string; stderr: string; images?: ImageInfo[] }> { + if (!image.includes(':')) { + image += ':latest'; + } + if (this.#dockerode) { + const stream = await this.#dockerode.pull(image); + if (!stream) { + throw new Error('No stream returned'); + } + return new Promise<{ stdout: string; stderr: string; images?: ImageInfo[] }>((resolve, reject) => { + const onFinished = (err: Error | null): void => { + if (err) { + return reject(err); + } + this.imageList() + .then(images => resolve({ stdout: `Image ${image} pulled`, stderr: '', images })) + .catch(reject); + }; + const lastShownProgress: { [id: string]: number } = {}; + const onProgress = ( + event: + | { + status: 'Downloading'; + progressDetail: { current: number; total: number }; + progress: string; + id: string; + } + | { + status: 'Download complete' | 'Verifying Checksum' | 'Pull complete'; + id: string; + } + | { + status: 'Extracting'; + progressDetail: { current: number; total: number }; + progress: string; + id: string; + }, + ): void => { + // {"status":"Downloading","progressDetail":{"current":109494080,"total":689664036},"progress":"[=======> ] 109.5MB/689.7MB","id":"29bce3058cea"} + // {"status":"Download complete","progressDetail":{},"id":"6859c690a072"} + // {"status":"Verifying Checksum","progressDetail":{},"id":"6859c690a072"} + // {"status":"Extracting","progressDetail":{"current":32,"total":32},"progress":"[======>] 32B/32B","id":"4f4fb700ef54"} + // {"status":"Pull complete","progressDetail":{},"id":"4f4fb700ef54"} + + if (!lastShownProgress || Date.now() - lastShownProgress[event.id] > 4000) { + if ( + event.status === 'Download complete' || + event.status === 'Pull complete' || + event.status === 'Verifying Checksum' + ) { + this.adapter.log.debug(`Image ${image}/${event.id}: ${event.status}`); + } else if (event.status === 'Downloading' || event.status === 'Extracting') { + this.adapter.log.debug( + `Pulling image ${image}/${event.id}: ${event.status} ${Math.round((event.progressDetail.current / event.progressDetail.total) * 1000) / 10}% of ${size2string(event.progressDetail.total)}`, + ); + } else { + this.adapter.log.debug(`Pulling image ${image}/${event.id}: ${JSON.stringify(event)}`); + } + lastShownProgress[event.id] = Date.now(); + } + }; + this.#dockerode!.modem.followProgress(stream, onFinished, onProgress); + }); + } + try { + const result = await this.#exec(`pull ${image}`); + const images = await this.imageList(); + if (!images.find(it => `${it.repository}:${it.tag}` === image)) { + throw new Error(`Image ${image} not found after pull`); + } + return { ...result, images }; + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** Autocomplete image names from Docker Hub */ + async imageNameAutocomplete(partialName: string): Promise< + { + name: string; + description: string; + isOfficial: boolean; + starCount: number; + }[] + > { + try { + // Read stars and descriptions + const { stdout } = await this.#exec( + `search ${partialName} --format "{{.Name}};{{.Description}};{{.IsOfficial}};{{.StarCount}}" --limit 50`, + ); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [name, description, isOfficial, starCount] = line.split(';'); + return { + name, + description, + isOfficial: isOfficial === 'true', + starCount: parseInt(starCount, 10) || 0, + }; + }); + } catch (e) { + this.adapter.log.debug(`Cannot search images: ${e.message}`); + return []; + } + } + + static getDockerodeConfig(config: ContainerConfig): Docker.ContainerCreateOptions { + let mounts: MountSettings[] | undefined; + if (config.mounts) { + for (const mount of config.mounts) { + let volumeOptions: + | { + NoCopy: boolean; + Labels: { [label: string]: string }; + DriverConfig: { + Name: string; + Options: { [option: string]: string }; + }; + Subpath?: string; + } + | undefined; + if (mount.volumeOptions) { + if (mount.volumeOptions.nocopy !== undefined) { + volumeOptions ||= {} as { + NoCopy: boolean; + Labels: { [label: string]: string }; + DriverConfig: { + Name: string; + Options: { [option: string]: string }; + }; + Subpath?: string; + }; + volumeOptions.NoCopy = mount.volumeOptions.nocopy; + } + if (mount.volumeOptions.labels) { + volumeOptions ||= {} as { + NoCopy: boolean; + Labels: { [label: string]: string }; + DriverConfig: { + Name: string; + Options: { [option: string]: string }; + }; + Subpath?: string; + }; + volumeOptions.Labels = mount.volumeOptions.labels; + } + } + let bindOptions: + | { + Propagation: MountPropagation; + } + | undefined; + if (mount.bindOptions) { + if (mount.bindOptions.propagation) { + bindOptions ||= {} as { + Propagation: MountPropagation; + }; + bindOptions.Propagation = mount.bindOptions.propagation; + } + } + + let tmpfsOptions: + | { + SizeBytes: number; + Mode: number; + } + | undefined; + if (mount.tmpfsOptions) { + if (mount.tmpfsOptions.size !== undefined) { + tmpfsOptions ||= {} as { + SizeBytes: number; + Mode: number; + }; + tmpfsOptions.SizeBytes = mount.tmpfsOptions.size; + } + if (mount.tmpfsOptions.mode !== undefined) { + tmpfsOptions ||= {} as { + SizeBytes: number; + Mode: number; + }; + tmpfsOptions.Mode = mount.tmpfsOptions.mode; + } + } + if (mount.source === true) { + throw new Error(`Mount source must be a string, but got boolean true`); + } + + const m: MountSettings = { + Target: mount.target, + Source: mount.source || '', + Type: mount.type as MountType, + ReadOnly: mount.readOnly, + Consistency: mount.consistency, + VolumeOptions: volumeOptions, + BindOptions: bindOptions, + TmpfsOptions: tmpfsOptions, + }; + mounts ||= []; + mounts.push(m); + } + } + if (!config.name) { + throw new Error(`Container name must be a string, but got boolean true`); + } + + return { + name: config.name, + Image: config.image, + Cmd: Array.isArray(config.command) + ? config.command + : typeof config.command === 'string' + ? [config.command] + : undefined, + Entrypoint: config.entrypoint, + Env: config.environment + ? Object.keys(config.environment).map(key => `${key}=${config.environment![key]}`) + : undefined, + // WorkingDir: config.workingDir, + // { '/data': {} } + Volumes: config.volumes?.reduce((acc, vol) => (acc[vol] = {}), {} as { [key: string]: object }), + Labels: config.labels, + ExposedPorts: config.ports + ? config.ports.reduce( + (acc, port) => { + acc[`${port.containerPort}/${port.protocol || 'tcp'}`] = {}; + return acc; + }, + {} as { [key: string]: object }, + ) + : undefined, + HostConfig: { + // Binds: config.binds, + PortBindings: config.ports + ? config.ports.reduce( + (acc, port) => { + acc[`${port.containerPort}/${port.protocol || 'tcp'}`] = [ + { + HostPort: port.hostPort ? port.hostPort.toString() : undefined, + HostIp: port.hostIP || undefined, + }, + ]; + return acc; + }, + {} as { [key: string]: Array<{ HostPort?: string; HostIp?: string }> }, + ) + : undefined, + Mounts: mounts, + NetworkMode: config.networkMode === true ? '' : config.networkMode || undefined, + // Links: config.links, + // Dns: config.dns, + // DnsOptions: config.dnsOptions, + // DnsSearch: config.dnsSearch, + ExtraHosts: config.extraHosts, + // VolumesFrom: config.volumesFrom, + Privileged: config.security?.privileged, + CapAdd: config.security?.capAdd, + CapDrop: config.security?.capDrop, + UsernsMode: config.security?.usernsMode, + IpcMode: config.security?.ipc, + PidMode: config.security?.pid, + GroupAdd: config.security?.groupAdd?.map(g => g.toString()), + ReadonlyRootfs: config.readOnly, + RestartPolicy: { + Name: config.restart?.policy || 'no', + MaximumRetryCount: config.restart?.maxRetries || 0, + }, + CpuShares: config.resources?.cpuShares, + CpuPeriod: config.resources?.cpuPeriod, + CpuQuota: config.resources?.cpuQuota, + CpusetCpus: config.resources?.cpus?.toString(), + Memory: config.resources?.memory, + MemorySwap: config.resources?.memorySwap, + MemoryReservation: config.resources?.memoryReservation, + // OomKillDisable: config.resources?.oomKillDisable, + // OomScoreAdj: config.resources?.oomScoreAdj, + LogConfig: config.logging + ? { + Type: config.logging.driver || 'json-file', + Config: config.logging.options || {}, + } + : undefined, + SecurityOpt: [ + ...(config.security?.seccomp ? [`seccomp=${config.security.seccomp}`] : []), + ...(config.security?.apparmor ? [`apparmor=${config.security.apparmor}`] : []), + ...(config.security?.noNewPrivileges ? ['no-new-privileges:true'] : []), + ], + Sysctls: config.sysctls, + Init: config.init, + }, + StopSignal: config.stop?.signal, + StopTimeout: config.stop?.gracePeriodSec, + Tty: config.tty, + OpenStdin: config.openStdin, + }; + } + + /** + * Create and start a container with the given configuration. No checks are done. + */ + async containerRun(config: ContainerConfig): Promise<{ stdout: string; stderr: string }> { + if (this.#dockerode) { + const container = await this.#dockerode.createContainer(DockerManager.getDockerodeConfig(config)); + await container.start(); + return { stdout: `Container ${config.name} started`, stderr: '' }; + } + + try { + return await this.#exec(`run ${DockerManager.toDockerRun(config)}`); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** + * Create a container with the given configuration without starting it. No checks are done. + */ + async containerCreate(config: ContainerConfig): Promise<{ stdout: string; stderr: string }> { + if (this.#dockerode) { + const container = await this.#dockerode.createContainer(DockerManager.getDockerodeConfig(config)); + return { stdout: `Container ${container.id} created`, stderr: '' }; + } + try { + return await this.#exec(`create ${DockerManager.toDockerRun(config, true)}`); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** + * Recreate a container + * + * This function checks if a container is running, stops it if necessary, + * removes it and creates a new one with the given configuration. + * The container is not started after creation. + * + * @param config new configuration + * @returns stdout and stderr of the create command + */ + async containerReCreate(config: ContainerConfig): Promise<{ stdout: string; stderr: string }> { + if (this.#dockerode) { + // Get if the container is running + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === config.name); + if (containerInfo) { + const container = this.#dockerode.getContainer(containerInfo.id); + if (containerInfo.status === 'running' || containerInfo.status === 'restarting') { + await container.stop(); + containers = await this.containerList(); + + if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) { + this.adapter.log.warn(`Cannot remove container: still running`); + throw new Error(`Container ${containerInfo.id} still running after stop`); + } + } + // Remove container + await container.remove(); + + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id)) { + this.adapter.log.warn(`Cannot remove container: still existing`); + throw new Error(`Container ${containerInfo.id} still found after remove`); + } + } + const newContainer = await this.#dockerode.createContainer(DockerManager.getDockerodeConfig(config)); + return { stdout: `Container ${newContainer.id} created`, stderr: '' }; + } + try { + // Get if the container is running + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === config.name); + if (containerInfo) { + if (containerInfo.status === 'running' || containerInfo.status === 'restarting') { + const stopResult = await this.#exec(`stop ${containerInfo.id}`); + containers = await this.containerList(); + + if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) { + this.adapter.log.warn(`Cannot remove container: ${stopResult.stderr || stopResult.stdout}`); + throw new Error(`Container ${containerInfo.id} still running after stop`); + } + } + // Remove container + const rmResult = await this.#exec(`rm ${containerInfo.id}`); + + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id)) { + this.adapter.log.warn(`Cannot remove container: ${rmResult.stderr || rmResult.stdout}`); + throw new Error(`Container ${containerInfo.id} still found after remove`); + } + } + return await this.#exec(`create ${DockerManager.toDockerRun(config, true)}`); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + async containerCreateCompose(compose: string): Promise<{ stdout: string; stderr: string }> { + try { + return await this.#exec(`compose -f ${compose} create`); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** List all images */ + async imageList(): Promise { + if (this.#dockerode) { + const images = await this.#dockerode.listImages(); + return images.map(img => { + const repoTag = img.RepoTags && img.RepoTags.length ? img.RepoTags[0] : ':'; + const [repository, tag] = repoTag.split(':'); + return { + repository, + tag, + id: img.Id.startsWith('sha256:') ? img.Id.substring(7, 19) : img.Id.substring(0, 12), + createdSince: new Date(img.Created * 1000).toISOString(), + size: img.Size, + }; + }); + } + try { + const { stdout } = await this.#exec( + 'images --format "{{.Repository}}:{{.Tag}};{{.ID}};{{.CreatedAt}};{{.Size}}"', + ); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [repositoryTag, id, createdSince, size] = line.split(';'); + const [repository, tag] = repositoryTag.split(':'); + return { + repository, + tag, + id, + createdSince, + size: this.#parseSize(size), + }; + }); + } catch (e) { + this.adapter.log.debug(`Cannot list images: ${e.message}`); + return []; + } + } + + /** Build an image from a Dockerfile */ + async imageBuild(dockerfilePath: string, tag: string): Promise<{ stdout: string; stderr: string }> { + if (this.#dockerode) { + try { + const stream = await this.#dockerode.buildImage(dockerfilePath, { + t: tag, + dockerfile: dockerfilePath, + }); + + return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { + let stdout = ''; + let stderr = ''; + + const onFinished = (err: Error | null): void => { + if (err) { + return reject(err); + } + resolve({ stdout, stderr }); + }; + const onProgress = (event: any): void => { + if (event.stream) { + stdout += event.stream; + } + if (event.error) { + stderr += event.error; + } + this.adapter.log.debug(JSON.stringify(event)); + }; + this.#dockerode!.modem.followProgress(stream, onFinished, onProgress); + }); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + try { + return await this.#exec(`build -t ${tag} -f ${dockerfilePath} .`); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** Tag an image with a new tag */ + async imageTag(imageId: ImageName, newTag: string): Promise<{ stdout: string; stderr: string }> { + try { + return await this.#exec(`tag ${imageId} ${newTag}`); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** Remove an image */ + async imageRemove(imageId: ImageName): Promise<{ stdout: string; stderr: string; images?: ImageInfo[] }> { + try { + const result = await this.#exec(`rmi ${imageId}`); + const images = await this.imageList(); + if (images.find(it => `${it.repository}:${it.tag}` === imageId)) { + return { stdout: '', stderr: `Image ${imageId} still found after deletion`, images }; + } + return { ...result, images }; + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + static rodeInspect2DockerImageInspect(data: Docker.ImageInspectInfo): DockerImageInspect { + return { + Id: data.Id.startsWith('sha256:') ? data.Id.substring(7, 19) : data.Id.substring(0, 12), + RepoTags: data.RepoTags, + RepoDigests: data.RepoDigests, + Parent: data.Parent, + Comment: data.Comment, + Created: data.Created, + DockerVersion: data.DockerVersion, + Author: data.Author, + Architecture: data.Architecture, + Os: data.Os, + Size: data.Size, + GraphDriver: { + Data: data.GraphDriver.Data as any, + Name: data.GraphDriver.Name, + }, + RootFS: data.RootFS, + Config: { + ...data.Config, + Entrypoint: Array.isArray(data.Config.Entrypoint) + ? data.Config.Entrypoint + : typeof data.Config.Entrypoint === 'string' + ? [data.Config.Entrypoint] + : [], + }, + }; + } + + /** Inspect an image */ + async imageInspect(imageId: ImageName): Promise { + if (this.#dockerode) { + const image = this.#dockerode.getImage(imageId); + const data = await image.inspect(); + return DockerManager.rodeInspect2DockerImageInspect(data); + } + + try { + const { stdout } = await this.#exec(`inspect ${imageId}`); + return JSON.parse(stdout)[0]; + } catch (e) { + this.adapter.log.debug(`Cannot inspect image: ${e.message.toString()}`); + return null; + } + } + + async imagePrune(): Promise<{ stdout: string; stderr: string }> { + if (this.#dockerode) { + try { + await this.#dockerode.pruneImages(); + return { stdout: 'Unused images pruned', stderr: '' }; + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + try { + return await this.#exec(`image prune -f`); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + #parseSize(sizeStr: string): number { + const units: { [key: string]: number } = { + B: 1, + KB: 1024, + MB: 1024 * 1024, + GB: 1024 * 1024 * 1024, + TB: 1024 * 1024 * 1024 * 1024, + }; + const match = sizeStr.match(/^([\d.]+)([KMGTP]?B)$/); + if (match) { + const value = parseFloat(match[1]); + const unit = match[2]; + return value * (units[unit] || 1); + } + return 0; + } + + /** + * Stop a container + * + * @param container Container name or ID + */ + async containerStop( + container: ContainerName, + ): Promise<{ stdout: string; stderr: string; containers?: ContainerInfo[] }> { + if (this.#dockerode) { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + const dockerContainer = this.#dockerode.getContainer(containerInfo.id); + await dockerContainer.stop(); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) { + throw new Error(`Container ${container} still running after stop`); + } + return { stdout: `Contained ${containerInfo.id} stopped`, stderr: '', containers }; + } + + try { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + + const result = await this.#exec(`stop ${containerInfo.id}`); + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id && it.status === 'running')) { + throw new Error(`Container ${container} still running after stop`); + } + return { ...result, containers }; + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** + * Start a container + * + * @param container Container name or ID + */ + async containerStart( + container: ContainerName, + ): Promise<{ stdout: string; stderr: string; containers?: ContainerInfo[] }> { + if (this.#dockerode) { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + const dockerContainer = this.#dockerode.getContainer(containerInfo.id); + await dockerContainer.start(); + containers = await this.containerList(); + if ( + containers.find( + it => it.id === containerInfo.id && it.status !== 'running' && it.status !== 'restarting', + ) + ) { + throw new Error(`Container ${container} still running after stop`); + } + return { stdout: `Container ${containerInfo.id} started`, stderr: '', containers }; + } + try { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + + const result = await this.#exec(`start ${containerInfo.id}`); + containers = await this.containerList(); + if ( + containers.find( + it => it.id === containerInfo.id && it.status !== 'running' && it.status !== 'restarting', + ) + ) { + throw new Error(`Container ${container} still running after stop`); + } + return { ...result, containers }; + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** + * Restart a container + * + * This function restarts a container by its name or ID. + * It accepts an optional timeout in seconds to wait before killing the container (default is 5 seconds). + * + * @param container Container name or ID + * @param timeoutSeconds Timeout in seconds to wait before killing the container (default: 5) + */ + async containerRestart( + container?: ContainerName, + timeoutSeconds?: number, + ): Promise<{ stdout: string; stderr: string }> { + container ||= this.getDefaultContainerName(); + if (this.#dockerode) { + const containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + const dockerContainer = this.#dockerode.getContainer(containerInfo.id); + await dockerContainer.restart({ t: timeoutSeconds || 5 }); + return { stdout: `Container ${containerInfo.id} restarted`, stderr: '' }; + } + try { + const containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + + return await this.#exec(`restart -t ${timeoutSeconds || 5} ${containerInfo.id}`); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** Find the IP address of a container, via which it can be reached from the host */ + async getIpOfContainer(containerName?: ContainerName): Promise { + containerName ||= this.getDefaultContainerName(); + const data = await this.containerInspect(containerName); + if (!data?.NetworkSettings?.Networks) { + throw new Error(`No network settings found for container ${containerName}`); + } + for (const n in data.NetworkSettings.Networks) { + if (data.NetworkSettings.Networks[n].IPAddress) { + return data.NetworkSettings.Networks[n].IPAddress; + } + } + throw new Error(`No IP address found for container ${containerName}`); + } + + /** + * Remove the container and if necessary, stop it first + * + * @param container Container name or ID + */ + async containerRemove( + container: ContainerName, + ): Promise<{ stdout: string; stderr: string; containers?: ContainerInfo[] }> { + try { + let containers = await this.containerList(); + // find ID of container + const containerInfo = containers.find(it => it.names === container || it.id === container); + if (!containerInfo) { + throw new Error(`Container ${container} not found`); + } + // ensure that container is stopped + if (containerInfo.status === 'running' || containerInfo.status === 'restarting') { + // stop container + const result = await this.#exec(`stop ${containerInfo.id}`); + if (result.stderr) { + throw new Error(`Cannot stop container ${container}: ${result.stderr}`); + } + } + + const result = await this.#exec(`rm ${container}`); + + containers = await this.containerList(); + if (containers.find(it => it.id === containerInfo.id)) { + throw new Error(`Container ${container} still found after stop`); + } + return { ...result, containers }; + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** + * List all containers + * + * @param all If true, list all containers. If false, list only running containers. Default is true. + */ + async containerList(all: boolean = true): Promise { + if (this.#dockerode) { + const containers = await this.#dockerode.listContainers({ all }); + return containers.map(cont => { + const statusKey: string = cont.State.toLowerCase(); + let status: ContainerInfo['status']; + if (statusKey === 'up') { + status = 'running'; + } else if (statusKey === 'exited') { + status = 'exited'; + } else if (statusKey === 'created') { + status = 'created'; + } else if (statusKey === 'paused') { + status = 'paused'; + } else if (statusKey === 'restarting') { + status = 'restarting'; + } else { + status = statusKey as ContainerInfo['status']; + } + + // Try to convert: Up 6 minutes, Up 6 hours, Up 6 days to minutes + const uptimeMatch = cont.Status.match(/Up (\d+) (seconds?|minutes?|hours?|days?)/); + let minutes = 0; + if (uptimeMatch) { + const value = parseInt(uptimeMatch[1], 10); + const unit = uptimeMatch[2]; + if (unit.startsWith('hour')) { + minutes = value * 60; + } else if (unit.startsWith('day')) { + minutes = value * 60 * 24; + } else if (unit.startsWith('second')) { + minutes = Math.ceil(value / 60); + } + } else if (cont.Status === 'Up About a minute') { + minutes = 1; + } + return { + id: cont.Id.substring(0, 12), + image: cont.Image, + command: cont.Command, + createdAt: new Date(cont.Created * 1000).toISOString(), + status, + uptime: minutes.toString(), + ports: cont.Ports.map( + p => + `${p.IP ? `${p.IP}:` : ''}${p.PublicPort ? `${p.PublicPort}->` : ''}${p.PrivatePort}/${p.Type}`, + ).join(', '), + names: cont.Names.map(n => (n.startsWith('/') ? n.substring(1) : n)).join(', '), + labels: cont.Labels || {}, + }; + }); + } + + try { + const { stdout } = await this.#exec( + `ps ${all ? '-a' : ''} --format "{{.Names}};{{.Status}};{{.ID}};{{.Image}};{{.Command}};{{.CreatedAt}};{{.Ports}};{{.Labels}}"`, + ); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [names, statusInfo, id, image, command, createdAt, ports, labels] = line.split(';'); + const [status, ...uptime] = statusInfo.split(' '); + let statusKey: ContainerInfo['status'] = status.toLowerCase() as ContainerInfo['status']; + if ((statusKey as string) === 'up') { + statusKey = 'running'; + } + return { + id, + image, + command, + createdAt, + status: statusKey, + uptime: uptime.join(' '), + ports, + names, + labels: + labels?.split(',').reduce( + (acc, label) => { + const [key, value] = label.split('='); + if (key && value) { + acc[key] = value; + } + return acc; + }, + {} as { [key: string]: string }, + ) || {}, + }; + }); + } catch (e) { + this.adapter.log.debug(`Cannot list containers: ${e.message}`); + return []; + } + } + + /** + * Get the logs of a container + * + * @param containerNameOrId Container name or ID + * @param options Options for logs + * @param options.tail Number of lines to show from the end of the logs + * @param options.follow If true, follow the logs (not implemented yet) + */ + async containerLogs( + containerNameOrId: ContainerName, + options: { tail?: number; follow?: boolean } = {}, + ): Promise { + if (this.#dockerode) { + try { + const container = this.#dockerode.getContainer(containerNameOrId); + const data = await container.logs({ + stdout: true, + stderr: true, + follow: false, + tail: options.tail || undefined, + }); + return data + .toString() + .split('\n') + .filter(line => line.trim() !== ''); + } catch (e) { + return e + .toString() + .split('\n') + .map((line: string): string => line.trim()); + } + } + + try { + const args = []; + if (options.tail !== undefined) { + args.push(`--tail ${options.tail}`); + } + if (options.follow) { + args.push(`--follow`); + throw new Error('Follow option is not implemented yet'); + } + const result = await this.#exec(`logs${args.length ? ` ${args.join(' ')}` : ''} ${containerNameOrId}`); + return (result.stdout || result.stderr).split('\n').filter(line => line.trim() !== ''); + } catch (e) { + return e + .toString() + .split('\n') + .map((line: string): string => line.trim()); + } + } + + static dockerodeInspect2DockerContainerInspect(data: Docker.ContainerInspectInfo): DockerContainerInspect { + return { + ...(data as unknown as DockerContainerInspect), + }; + } + + /** Inspect a container */ + async containerInspect(containerNameOrId: string): Promise { + if (this.#dockerode) { + try { + const container = this.#dockerode.getContainer(containerNameOrId); + const dResult = await container.inspect(); + + const result = DockerManager.dockerodeInspect2DockerContainerInspect(dResult); + if (result.State.Running) { + result.Stats = (await this.containerGetRamAndCpuUsage(containerNameOrId)) || undefined; + } + return result; + } catch (e) { + this.adapter.log.debug(`Cannot inspect container: ${e.message.toString()}`); + return null; + } + } + try { + const { stdout } = await this.#exec(`inspect ${containerNameOrId}`); + const result = JSON.parse(stdout)[0] as DockerContainerInspect; + if (result.State.Running) { + result.Stats = (await this.containerGetRamAndCpuUsage(containerNameOrId)) || undefined; + } + return result; + } catch (e) { + this.adapter.log.debug(`Cannot inspect container: ${e.message.toString()}`); + return null; + } + } + + async containerPrune(): Promise<{ stdout: string; stderr: string }> { + if (this.#dockerode) { + try { + const result = await this.#dockerode.pruneContainers(); + return { stdout: `Containers pruned: ${result.ContainersDeleted?.join(', ') || 'none'}`, stderr: '' }; + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + try { + return await this.#exec(`container prune -f`); + } catch (e) { + return { stdout: '', stderr: e.message.toString() }; + } + } + + /** + * Build a docker run command string from ContainerConfig + */ + static toDockerRun(config: ContainerConfig, create?: boolean): string { + const args: string[] = []; + + // detach / interactive + if (config.detach !== false && !create) { + // default is true + args.push('-d'); + } + if (config.tty) { + args.push('-t'); + } + if (config.stdinOpen) { + args.push('-i'); + } + if (config.removeOnExit) { + args.push('--rm'); + } + + // name + if (config.name) { + args.push('--name', config.name); + } + + // hostname / domain + if (config.hostname) { + args.push('--hostname', config.hostname); + } + if (config.domainname) { + args.push('--domainname', config.domainname); + } + + // environment + if (config.environment) { + for (const [key, value] of Object.entries(config.environment)) { + if (key && value) { + args.push('-e', `${key}=${value}`); + } + } + } + if (config.envFile) { + for (const file of config.envFile) { + args.push('--env-file', file); + } + } + + // labels + if (config.labels) { + for (const [key, value] of Object.entries(config.labels)) { + args.push('--label', `${key}=${value}`); + } + } + + // ports + if (config.publishAllPorts) { + args.push('-P'); + } + if (config.ports) { + for (const p of config.ports) { + if (!p.containerPort) { + continue; + } + const mapping = + (p.hostIP ? `${p.hostIP}:` : '') + + (p.hostPort ? `${p.hostPort}:` : '') + + p.containerPort + + (p.protocol ? `/${p.protocol}` : ''); + args.push('-p', mapping); + } + } + + // volumes / mounts + if (config.volumes) { + for (const v of config.volumes) { + args.push('-v', v); + } + } + if (config.mounts) { + for (const m of config.mounts) { + let mount = `type=${m.type},target=${m.target}`; + if (m.source) { + mount += `,source=${m.source}`; + } + if (m.readOnly) { + mount += `,readonly`; + } + args.push('--mount', mount); + } + } + + // restart policy + if (config.restart?.policy) { + const val = + config.restart.policy === 'on-failure' && config.restart.maxRetries + ? `on-failure:${config.restart.maxRetries}` + : config.restart.policy; + args.push('--restart', val); + } + + // user & workdir + if (config.user) { + args.push('--user', String(config.user)); + } + if (config.workdir) { + args.push('--workdir', config.workdir); + } + + // logging + if (config.logging?.driver) { + args.push('--log-driver', config.logging.driver); + if (config.logging.options) { + for (const [k, v] of Object.entries(config.logging.options)) { + args.push('--log-opt', `${k}=${v}`); + } + } + } + + // security + if (config.security?.privileged) { + args.push('--privileged'); + } + if (config.security?.capAdd) { + for (const cap of config.security.capAdd) { + args.push('--cap-add', cap); + } + } + if (config.security?.capDrop) { + for (const cap of config.security.capDrop) { + args.push('--cap-drop', cap); + } + } + if (config.security?.noNewPrivileges) { + args.push('--security-opt', 'no-new-privileges'); + } + if (config.security?.apparmor) { + args.push('--security-opt', `apparmor=${config.security.apparmor}`); + } + + // network + if (config.networkMode && typeof config.networkMode === 'string') { + args.push('--network', config.networkMode); + } + + // extra hosts + if (config.extraHosts) { + for (const host of config.extraHosts as any[]) { + if (typeof host === 'string') { + args.push('--add-host', host); + } else { + args.push('--add-host', `${host.host}:${host.ip}`); + } + } + } + + // sysctls + if (config.sysctls) { + for (const [k, v] of Object.entries(config.sysctls)) { + args.push('--sysctl', `${k}=${v}`); + } + } + + // stop signal / timeout + if (config.stop?.signal) { + args.push('--stop-signal', config.stop.signal); + } + if (config.stop?.gracePeriodSec !== undefined) { + args.push('--stop-timeout', String(config.stop.gracePeriodSec)); + } + + // resources + if (config.resources?.cpus) { + args.push('--cpus', String(config.resources.cpus)); + } + if (config.resources?.memory) { + args.push('--memory', String(config.resources.memory)); + } + + // image + if (!config.image) { + throw new Error('ContainerConfig.image is required for docker run'); + } + args.push(config.image); + + // command override + if (config.command) { + if (Array.isArray(config.command)) { + args.push(...config.command); + } else { + args.push(config.command); + } + } + + return args.join(' '); + } + + async networkList(): Promise { + if (this.#dockerode) { + const networks = await this.#dockerode.listNetworks(); + return networks.map(net => ({ + name: net.Name, + id: net.Id, + driver: net.Driver as NetworkDriver, + scope: net.Scope, + })); + } + // docker network ls + try { + const { stdout } = await this.#exec(`network ls --format "{{.Name}};{{.ID}};{{.Driver}};{{.Scope}}"`); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [name, id, driver, scope] = line.split(';'); + return { name, id, driver: driver as NetworkDriver, scope }; + }); + } catch (e) { + this.adapter.log.debug(`Cannot list networks: ${e.message.toString()}`); + return []; + } + } + + async networkCreate( + name: string, + driver?: NetworkDriver, + ): Promise<{ stdout: string; stderr: string; networks?: NetworkInfo[] }> { + if (this.#dockerode) { + const net = await this.#dockerode.createNetwork({ Name: name, Driver: driver || 'bridge' }); + const networks = await this.networkList(); + if (!networks.find(it => it.name === name)) { + throw new Error(`Network ${name} not found after creation`); + } + return { stdout: `Network ${net.id} created`, stderr: '', networks }; + } + + const result = await this.#exec(`network create ${driver ? `--driver ${driver}` : ''} ${name}`); + const networks = await this.networkList(); + if (!networks.find(it => it.name === name)) { + throw new Error(`Network ${name} not found after creation`); + } + return { ...result, networks }; + } + + async networkRemove(networkId: string): Promise<{ stdout: string; stderr: string; networks?: NetworkInfo[] }> { + const result = await this.#exec(`network remove ${networkId}`); + const networks = await this.networkList(); + if (networks.find(it => it.id === networkId)) { + throw new Error(`Network ${networkId} still found after deletion`); + } + return { ...result, networks }; + } + + async networkPrune(): Promise<{ stdout: string; stderr: string; networks?: NetworkInfo[] }> { + if (this.#dockerode) { + const result = await this.#dockerode.pruneNetworks(); + const networks = await this.networkList(); + return { stdout: `Networks pruned`, stderr: JSON.stringify(result), networks }; + } + const result = await this.#exec(`network prune -f`); + const networks = await this.networkList(); + return { ...result, networks }; + } + + /** List all volumes */ + async volumeList(): Promise { + if (this.#dockerode) { + const volumesData = await this.#dockerode.listVolumes(); + return (volumesData.Volumes || []).map(vol => ({ + name: vol.Name, + driver: vol.Driver as VolumeDriver, + volume: vol.Mountpoint, + })); + } + // docker network ls + try { + const { stdout } = await this.#exec(`volume ls --format "{{.Name}};{{.Driver}};{{.Mountpoint}}"`); + return stdout + .split('\n') + .filter(line => line.trim() !== '') + .map(line => { + const [name, driver, volume] = line.split(';'); + return { name, driver: driver as VolumeDriver, volume }; + }); + } catch (e) { + this.adapter.log.debug(`Cannot list networks: ${e.message.toString()}`); + return []; + } + } + + async volumeCopyTo(volumeName: string, sourcePath: string): Promise<{ stdout: string; stderr: string }> { + const tempContainerName = `iobroker_temp_copy_${Date.now()}`; + if (this.#dockerode) { + // Check if alpine image is there + const images = await this.imageList(); + if (!images.find(img => img.repository === 'alpine')) { + const pullResult = await this.imagePull('alpine'); + if (pullResult.stderr) { + return { stdout: '', stderr: `Cannot pull alpine image: ${pullResult.stderr}` }; + } + } + + // create a temporary container with volume mounted + const container = await this.#dockerode.createContainer({ + Image: 'alpine', + name: tempContainerName, + Cmd: ['sleep', '30'], + HostConfig: { + Binds: [`${volumeName}:/data`], + }, + }); + try { + await container.start(); + // Lazy loading tar-fs + if (!this.#tarPack) { + await import('tar-fs') + .then(tarFs => (this.#tarPack = tarFs.default.pack)) + .catch(e => this.adapter.log.error(`Cannot import tar-fs package: ${e.message}`)); + } + if (!this.#tarPack) { + throw new Error('Cannot load tar-fs package'); + } + // use dockerode to copy files + const pack = this.#tarPack(sourcePath); + await container.putArchive(pack, { path: '/data' }); + return { stdout: 'Data copied to volume', stderr: '' }; + } catch (e) { + return { stdout: '', stderr: `Cannot copy data to volume: ${e.message}` }; + } finally { + // remove temporary container + try { + await container.stop(); + await container.remove({ force: true }); + } catch (e) { + this.adapter.log.warn(`Cannot remove temporary container ${tempContainerName}: ${e.message}`); + } + } + } + + // create a temporary container with volume mounted + const createResult = await this.#exec( + `create -v ${volumeName}:/data --name ${tempContainerName} alpine sleep 60`, + ); + if (createResult.stderr) { + return { stdout: '', stderr: `Cannot create temporary container: ${createResult.stderr}` }; + } + try { + const copyResult = await this.#exec(`cp ${sourcePath} ${tempContainerName}:/data/`); + if (copyResult.stderr) { + return { stdout: '', stderr: `Cannot copy data to volume: ${copyResult.stderr}` }; + } + return { stdout: 'Data copied to volume', stderr: '' }; + } finally { + // remove temporary container + await this.#exec(`rm -f ${tempContainerName}`); + } + } + + /** + * Create a volume + * + * @param name Volume name + * @param driver Volume driver + * @param volume Volume options (depends on driver) + */ + async volumeCreate( + name: string, + driver?: VolumeDriver, + volume?: string, + ): Promise<{ stdout: string; stderr: string; volumes?: VolumeInfo[] }> { + if (this.#dockerode) { + const vol = await this.#dockerode.createVolume({ + Name: name, + Driver: driver || 'local', + DriverOpts: volume ? { device: volume, o: 'bind', type: 'none' } : {}, + }); + const volumes = await this.volumeList(); + if (!volumes.find(it => it.name === name)) { + throw new Error(`Network ${name} not found after creation`); + } + return { stdout: `Volume ${vol.Name} created`, stderr: '', volumes }; + } + let result: { stdout: string; stderr: string }; + if (driver === 'local' || !driver) { + if (volume) { + result = await this.#exec( + `volume create local --opt type=none --opt device=${volume} --opt o=bind ${name}`, + ); + } else { + result = await this.#exec(`volume create ${name}`); + } + } else { + throw new Error('not implemented'); + } + + const volumes = await this.volumeList(); + if (!volumes.find(it => it.name === name)) { + throw new Error(`Network ${name} not found after creation`); + } + return { ...result, volumes }; + } + + /** Remove a volume */ + async volumeRemove(volumeName: string): Promise<{ stdout: string; stderr: string; volumes?: VolumeInfo[] }> { + const result = await this.#exec(`volume remove ${volumeName}`); + const volumes = await this.volumeList(); + if (volumes.find(it => it.name === volumeName)) { + throw new Error(`Volume ${volumeName} still found after deletion`); + } + return { ...result, volumes }; + } + + /** Prune unused volumes */ + async volumePrune(): Promise<{ stdout: string; stderr: string; volumes?: VolumeInfo[] }> { + if (this.#dockerode) { + const result = await this.#dockerode.pruneVolumes(); + const volumes = await this.volumeList(); + return { stdout: `Volumes pruned`, stderr: JSON.stringify(result), volumes }; + } + const result = await this.#exec(`volume prune -f`); + const volumes = await this.volumeList(); + return { ...result, volumes }; + } + + /** Stop own containers if necessary */ + async destroy(): Promise { + if (this.#monitoringInterval) { + clearInterval(this.#monitoringInterval); + this.#monitoringInterval = null; + } + + for (const container of this.#ownContainers) { + if (container.iobEnabled !== false && container.iobStopOnUnload && container.name) { + this.adapter.log.info(`Stopping own container ${container.name} on destroy`); + try { + await this.containerStop(container.name); + } catch (e) { + this.adapter.log.warn(`Cannot stop own container ${container.name} on destroy: ${e.message}`); + } + } + } + } +} diff --git a/src/lib/aggregate.ts b/src/lib/aggregate.ts new file mode 100644 index 00000000..ee026d09 --- /dev/null +++ b/src/lib/aggregate.ts @@ -0,0 +1,1617 @@ +// THIS file should be identical with SQL and history adapter's one + +import type { GetHistoryOptions, InternalHistoryOptions, IobDataEntry, TimeInterval } from './types'; + +/** + * Calculate the square of triangle between two data points on chart + * val | | + * | | /-----|----- + * | | /__--/######| + * |----|--/############| + * | |###############| + * +----o---------------n--->time + * Square is the #, deltaT = x2 - x1, DeltaY = y2 - y1 + */ +export function calcDiff(oldVal: IobDataEntry, newVal: IobDataEntry): { square: number; deltaT: number } { + // (Xnew - Xold) / 3600000 (to hours) + const deltaT = (newVal.ts - oldVal.ts) / 3_600_000; // ms => hours + + // if deltaT is negative, we have a problem as the time cannot go back + if (deltaT < 0) { + return { square: 0, deltaT: 0 }; + } + const square = ((newVal.val || 0) + (oldVal.val || 0)) * (deltaT * 0.5); + + return { square, deltaT }; +} + +function interpolate2points(p1: IobDataEntry, p2: IobDataEntry, ts: number): number { + const dx = p2.ts - p1.ts; + // threat null as zero + const dy = (p2.val || 0) - (p1.val || 0); + if (!dx) { + return p1.val!; + } + return (dy * (ts - p1.ts)) / dx + p1.val!; +} + +export function initAggregate( + initialOptions: GetHistoryOptions, + id?: string, + timeIntervals?: TimeInterval[], + log?: (text: string) => void, +): InternalHistoryOptions { + const options: InternalHistoryOptions = initialOptions as InternalHistoryOptions; + options.log = log; + options.id = id; // id is needed for because of addId option + if (!options.log) { + options.log = () => {}; + // To save the complex outputs + options.logDebug = false; + } + + // step; // 1 Step is 1 second + if (options.step === null || options.step === undefined || options.step <= 0) { + options.step = (options.end! - options.start!) / options.count!; + } + + // Limit 2000 + if ((options.end! - options.start!) / options.step > options.limit!) { + options.step = (options.end! - options.start!) / options.limit!; + } + + if (timeIntervals) { + options.timeIntervals = timeIntervals; + options.maxIndex = options.timeIntervals.length - 1; + } else { + // MaxIndex is the index, that can really called without -1 + options.maxIndex = Math.ceil((options.end! - options.start!) / options.step - 1); + } + options.processing = []; + options.result = []; // finalResult + options.averageCount = []; + options.quantileDataPoints = []; + options.integralDataPoints = []; + options.totalIntegralDataPoints = []; + options.aggregate = options.aggregate || 'minmax'; + options.overallLength = 0; + options.currentTimeInterval = 0; + + if (options.aggregate === 'percentile') { + if (typeof options.percentile !== 'number' || options.percentile < 0 || options.percentile > 100) { + options.percentile = 50; + } + options.quantile = options.percentile / 100; // Internally we use quantile for percentile too + } + if (options.aggregate === 'quantile') { + if (typeof options.quantile !== 'number' || options.quantile < 0 || options.quantile > 1) { + options.quantile = 0.5; + } + } + if (options.aggregate === 'integral') { + if (typeof options.integralUnit !== 'number' || options.integralUnit <= 0) { + options.integralUnit = 60; + } + options.integralUnit *= 1000; // Convert to milliseconds + } + + if (options.logDebug && options.log) { + options.log( + `Initialize: maxIndex = ${options.maxIndex}, step = ${!timeIntervals ? options.step : 'smart'}, start = ${options.start}, end = ${options.end}`, + ); + } + + // pre-fill the result with timestamps (add one before start and one after end) + try { + options.processing.length = options.maxIndex + 2; + } catch (err) { + err.message += `: ${options.maxIndex + 2}`; + throw err; + } + // We define the array length, but do not prefill values, we do that on runtime when needed + options.processing[0] = { + val: { ts: null, val: null }, + max: { ts: null, val: null }, + min: { ts: null, val: null }, + start: { ts: null, val: null }, + end: { ts: null, val: null }, + }; + options.processing[options.maxIndex + 2] = { + val: { ts: null, val: null }, + max: { ts: null, val: null }, + min: { ts: null, val: null }, + start: { ts: null, val: null }, + end: { ts: null, val: null }, + }; + + if (options.aggregate === 'average') { + options.averageCount[0] = 0; + options.averageCount[options.maxIndex + 2] = 0; + } + + if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { + options.quantileDataPoints[0] = []; + options.quantileDataPoints[options.maxIndex + 2] = []; + } + if (options.aggregate === 'integral') { + options.integralDataPoints[0] = []; + options.integralDataPoints[options.maxIndex + 2] = []; + } + return options; +} + +export function aggregation( + options: InternalHistoryOptions, + data: IobDataEntry[], +): { + processing: { + val: { ts: number | null; val: number | null }; + max: { ts: number | null; val: number | null }; + min: { ts: number | null; val: number | null }; + start: { ts: number | null; val: number | null }; + end: { ts: number | null; val: number | null }; + }[]; + step: number; + sourceLength: number; +} { + let index: number; + let preIndex: number; + + let collectedTooEarlyData: IobDataEntry[] = []; + let collectedTooLateData: IobDataEntry[] = []; + let preIndexValueFound = false; + let postIndexValueFound = false; + + for (let i = 0; i < data.length; i++) { + if (!data[i]) { + continue; + } + if (typeof data[i].ts !== 'number') { + data[i].ts = parseInt(data[i].ts as unknown as string, 10); + } + + if (options.timeIntervals) { + // We have specific time intervals and collects all data according to this information + const dataTs = data[i].ts; + // Find a time interval for this timestamp + if (dataTs < options.start!) { + index = 0; + } else if (dataTs >= options.end!) { + index = options.timeIntervals.length + 1; + } else if (dataTs >= options.timeIntervals[options.currentTimeInterval!].end) { + // Look for the next interval + options.currentTimeInterval!++; + while (options.currentTimeInterval! < options.timeIntervals.length) { + if ( + options.timeIntervals[options.currentTimeInterval!].start <= dataTs && + dataTs < options.timeIntervals[options.currentTimeInterval!].end + ) { + break; + } + options.currentTimeInterval!++; + } + index = options.currentTimeInterval! + 1; + } else { + index = options.currentTimeInterval! + 1; + } + } else { + // Our intervals are equidistant and we can calculate them + preIndex = Math.floor((data[i].ts - options.start!) / options.step!); + + // store all border values + if (preIndex < 0) { + index = 0; + // if the ts is even earlier than the "pre-interval" ignore it, else we collect all data there + if (preIndex < -1) { + collectedTooEarlyData.push(data[i]); + continue; + } + preIndexValueFound = true; + } else if (preIndex > options.maxIndex!) { + index = options.maxIndex! + 2; + // if the ts is even later than the "post-interval" ignore it, else we collect all data there + if (preIndex > options.maxIndex! + 1) { + collectedTooLateData.push(data[i]); + continue; + } + postIndexValueFound = true; + } else { + index = preIndex + 1; + } + options.overallLength!++; + } + + // Init data for time slot + if (options.processing![index] === undefined) { + // lazy initialization of data structure + options.processing![index] = { + val: { ts: null, val: null }, + max: { ts: null, val: null }, + min: { ts: null, val: null }, + start: { ts: null, val: null }, + end: { ts: null, val: null }, + }; + + if (options.aggregate === 'average' || options.aggregate === 'count') { + options.averageCount![index] = 0; + } + + if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { + options.quantileDataPoints![index] = []; + } + if (options.aggregate === 'integral') { + options.integralDataPoints![index] = []; + } + } + + aggregationLogic(data[i], index, options); + } + + // If no data was found in the pre-interval, but we have earlier data, we put the latest of them in the pre-interval + if (!preIndexValueFound && collectedTooEarlyData.length > 0) { + collectedTooEarlyData = collectedTooEarlyData.sort(sortByTs); + options.overallLength!++; + aggregationLogic(collectedTooEarlyData[collectedTooEarlyData.length - 1], 0, options); + } + // If no data was found in the post-interval, but we have later data, we put the earliest of them in the post-interval + if (!postIndexValueFound && collectedTooLateData.length > 0) { + collectedTooLateData = collectedTooLateData.sort(sortByTs); + options.overallLength!++; + aggregationLogic(collectedTooLateData[0], options.maxIndex! + 2, options); + } + + return { processing: options.processing!, step: options.step!, sourceLength: data.length }; +} + +/** Execute logic for every entry in the initial series array */ +function aggregationLogic(data: IobDataEntry, index: number, options: InternalHistoryOptions): void { + if (!options.processing || !options.processing[index]) { + if (options.logDebug && options.log) { + options.log(`Data index ${index} not initialized, ignore!`); + } + return; + } + + if (options.aggregate !== 'minmax' && !options.processing[index].val.ts) { + if (options.timeIntervals) { + if (index === 0) { + // If it is pre interval, make an estimation + options.processing[0].val.ts = + options.timeIntervals[0].start - + Math.round((options.timeIntervals[0].end - options.timeIntervals[0].start) / 2); + } else if (index > options.timeIntervals.length) { + // If it is post interval, make an estimation + options.processing[index].val.ts = + options.timeIntervals[options.timeIntervals.length - 1].end + + Math.round( + (options.timeIntervals[options.timeIntervals.length - 1].end - + options.timeIntervals[options.timeIntervals.length - 1].start) / + 2, + ); + } else { + // Get the middle of the interval + options.processing[index].val.ts = + options.timeIntervals[index - 1].start + + Math.round((options.timeIntervals[index - 1].end - options.timeIntervals[index - 1].start) / 2); + } + } else { + options.processing[index].val.ts = Math.round(options.start! + (index - 1 + 0.5) * options.step!); + } + } + + if (options.aggregate === 'max') { + if (options.processing[index].val.val === null || options.processing[index].val.val < data.val!) { + options.processing[index].val.val = data.val; + } + } else if (options.aggregate === 'min') { + if (options.processing[index].val.val === null || options.processing[index].val.val > data.val!) { + options.processing[index].val.val = data.val; + } + } else if (options.aggregate === 'average') { + options.processing[index].val.val! += parseFloat(data.val as unknown as string); + options.averageCount![index]++; + } else if (options.aggregate === 'count') { + options.averageCount![index]++; + } else if (options.aggregate === 'total') { + options.processing[index].val.val! += parseFloat(data.val as unknown as string); + } else if (options.aggregate === 'minmax') { + if (options.processing[index].min.ts === null) { + options.processing[index].min.ts = data.ts; + options.processing[index].min.val = data.val; + + options.processing[index].max.ts = data.ts; + options.processing[index].max.val = data.val; + + options.processing[index].start.ts = data.ts; + options.processing[index].start.val = data.val; + + options.processing[index].end.ts = data.ts; + options.processing[index].end.val = data.val; + } else { + if (data.val !== null && data.val !== undefined) { + if (data.val > options.processing[index].max.val!) { + options.processing[index].max.ts = data.ts; + options.processing[index].max.val = data.val; + } else if (data.val < options.processing[index].min.val!) { + options.processing[index].min.ts = data.ts; + options.processing[index].min.val = data.val; + } + if (data.ts > options.processing[index].end.ts!) { + options.processing[index].end.ts = data.ts; + options.processing[index].end.val = data.val; + } + } else { + if (data.ts > options.processing[index].end.ts!) { + options.processing[index].end.ts = data.ts; + options.processing[index].end.val = null; + } + } + } + } else if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { + options.quantileDataPoints![index].push(data.val || 0); + if (options.logDebug && options.log) { + options.log(`Quantile ${index}: Add ts= ${data.ts} val=${data.val}`); + } + } else if (options.aggregate === 'integral') { + options.integralDataPoints![index].push(data); + if (options.logDebug && options.log) { + options.log(`Integral ${index}: Add ts= ${data.ts} val=${data.val}`); + } + } else if (options.aggregate === 'integralTotal') { + options.totalIntegralDataPoints?.push(data); + } +} + +/** + * finishAggregationForIntegralEx + * + * Purpose: + * - Calculate integrals per defined time intervals (smart intervals mode per hour). + * - Options must contain `timeIntervals` and `integralDataPoints`. + * - Produces `options.result` as an array with one entry per `timeIntervals` element. + * + * Input: + * - options: InternalHistoryOptions already populated by previous aggregation steps. + * Required fields: + * - timeIntervals: TimeInterval[] (array of intervals to calculate over) + * - integralDataPoints: IobDataEntry[][] where length == timeIntervals.length + 2 + * (one pre-interval array at index 0, one post-interval array at last index) + * - logDebug (optional) and log (optional) for detailed debug output + * + * Output: + * - options.result: IobDataEntry[] of length = timeIntervals.length + * Each entry: + * - ts: representative timestamp (interval midpoint) + * - val: integral value for that interval (number) + * - time: ISO string of ts when logDebug is enabled (optional) + * + * Behaviour / Algorithm overview: + * 1. Find the latest data point that lies in the pre-interval (index 0). + * That point is used as the `current` known value to interpolate forward. + * 2. Initialize finalResult with one entry per time interval; each entry's `val` starts at 0 + * and `ts` is set to the interval midpoint (used as representative timestamp). + * 3. If no pre-interval `current` point exists, nothing to integrate -> return finalResult of zeros. + * 4. Iterate intervals from the first interval that might contain data (index found in step 1): + * - For interval i, obtain the bucket workDP stored at `integralDataPoints[i + 1]`. + * - If bucket has data and `current` exists: + * * Interpolate a point at interval start using linear interpolation between `current` and bucket[0]. + * * Insert that interpolated start point at the beginning of bucket. + * * Update `current` to the last point in this bucket (the newest in that bucket). + * - Find the next known data point `next` in later buckets (search forward) and remember its interval index. + * - If no `next` found, assume no further data -> stop filling remaining intervals. + * - Ensure the bucket has at least a start point; if empty, compute and push a start point interpolated between `current` and `next`. + * - Always compute and push an end point for the interval at `workTi.end - 1`, interpolated between `current` and `next`. + * 5. After all intervals that can be filled have a start and end point, compute the integral for each interval: + * - For each bucket (workDP) compute sum of trapezoidal areas between consecutive points using `calcDiff(...).square`. + * - Set finalResult[i].val = sum for that interval. + * + * Key assumptions and details: + * - `integralDataPoints` array length is `timeIntervals.length + 2`: + * index 0 = pre-interval (values before start), + * indices 1..timeIntervals.length = buckets for each interval, + * index timeIntervals.length + 1 = post-interval (values after end) + * - The function uses linear interpolation (via interpolate2points) to estimate values at interval borders. + * - Interval representative timestamp (`ts`) is computed as start + round((end - start) / 2). + * - End timestamp for each interval when interpolating is `workTi.end - 1` (inclusive millisecond before next interval). + * - If no `current` (no pre-interval data), return early leaving all zeros. + * - If no `next` is found while iterating forward, the function stops filling further intervals, + * because it assumes no future data to interpolate to. + * - Debug logs (if `options.logDebug`) include created interpolated points and interval contents. + * + * Edge cases: + * - Zero-length intervals (end == start) are not expected in `timeIntervals`. + * - If `interpolate2points` gets identical timestamps it returns p1.val (guard in helper). + * - The function mutates `options.integralDataPoints` by inserting start/end interpolated entries. + * - If `workDP` remains undefined for an interval, `finalResult` keeps that interval's value as 0. + */ +export function finishAggregationForIntegralEx(options: InternalHistoryOptions): void { + // The first interval is pre-interval: its data is used only to determine the initial "current" value. + let index = 0; + let workDP: IobDataEntry[]; + let next: IobDataEntry | null = null; + let current: IobDataEntry | null = null; + const finalResult: IobDataEntry[] = []; + + if (!options.integralDataPoints || !options.timeIntervals) { + throw new Error('finishAggregationForIntegralEx: options.integralDataPoints or options.timeIntervals missing'); + } + + // 1) Find the first non-empty integral bucket starting from pre-interval (index 0). + // The last element of the first non-empty bucket is the newest value before options.start. + // We must remember, that options.integralDataPoints is longer than options.timeIntervals on 2. It hast pre- and post- values + do { + workDP = options.integralDataPoints[index]; + if (workDP?.length) { + // It must be the newest value before options.start + current = workDP[workDP.length - 1]; + break; + } + index++; + } while (index < options.integralDataPoints.length); + + // 2) Initialize finalResult entries for every time interval with ts = midpoint and val = 0 + for (let i = 0; i < options.timeIntervals.length; i++) { + const oneInterval: IobDataEntry = { + // compute midpoint deterministically: start + round((end - start) / 2) + ts: + options.timeIntervals[i].start + + Math.round((options.timeIntervals[i].end - options.timeIntervals[i].start) / 2), + val: 0, + }; + // Add ISO string for easier debugging if requested + oneInterval.time = new Date(oneInterval.ts).toISOString(); + finalResult.push(oneInterval); + } + + // assign preliminary result array back to options + options.result = finalResult; + + // 3) If there is no value before start, nothing to integrate -> keep zeroed result + if (!current) { + return; + } + + let workTi: TimeInterval; + // Holds index of the interval where the next known data was found during forward search + let nextIntervalIndex: number | null = null; + + // 4) Iterate intervals starting from the one where we might have data (index) + // Calculate for every interval the start + for (let i = index; i < options.timeIntervals.length; i++) { + // bucket for interval i is stored at integralDataPoints[i + 1] + workDP = options.integralDataPoints[i + 1]; + workTi = options.timeIntervals[i]; + + // If this bucket already contains points, and we have a prior 'current', insert an interpolated start point + if (workDP?.length && current) { + // calculate the first value in this interval + const firstValue = interpolate2points(current, workDP[0], workTi.start); + const time: IobDataEntry = { ts: workTi.start, val: firstValue }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + + // Insert at beginning to make sure bucket starts at interval boundary + workDP.unshift(time); + // Update current to newest in this bucket for subsequent interpolation + current = workDP[workDP.length - 1]; + } + + // Find the next known datapoint in later buckets (only search if previous found next is not usable) + if (nextIntervalIndex === null || nextIntervalIndex <= i) { + next = null; + let j = i + 1; + do { + // check bucket j + 1 because of pre- / post-encodings (buckets for intervals are shifted by 1) + if (options.integralDataPoints[j + 1]?.length) { + // next is the earliest datapoint in that later bucket + next = options.integralDataPoints[j + 1][0]; + nextIntervalIndex = j; + break; + } + j++; + } while (j <= options.timeIntervals.length); + } + + // If no next datapoint exists, assume no more future data available -> stop filling further intervals + if (!next) { + // We assume, that no more data will come + break; + } + + // Ensure the bucket exists + options.integralDataPoints[i + 1] = options.integralDataPoints[i + 1] || []; + workDP = options.integralDataPoints[i + 1]; + + // If this bucket is empty, compute an interpolated start point between current and next + if (!workDP.length) { + // place first value + // calculate the first value in this interval + const firstValue = interpolate2points(current, next, workTi.start); + const time: IobDataEntry = { ts: workTi.start, val: firstValue }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + workDP.push(time); + } + + // Always compute an interpolated end point at workTi.end - 1 to make the bucket closed for integral calculation + const lastValue = interpolate2points(current, next, workTi.end - 1); + const time: IobDataEntry = { ts: workTi.end - 1, val: lastValue }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + workDP.push(time); + } + + // 5) Now that start and end points are ensured where possible, compute integral for each interval + for (let i = 0; i < options.timeIntervals.length; i++) { + workDP = options.integralDataPoints[i + 1]; + if (!workDP) { + // leave finalResult[i].val as 0 when no bucket exists + continue; + } + finalResult[i].val = calcIntegralForPeriod(workDP); + } +} + +/** + * This function calculates integral for one time period. + */ +function calcIntegralForPeriod(workDP: IobDataEntry[]): number { + let sum = 0; + for (let i = 0; i < workDP.length - 1; i++) { + sum += calcDiff(workDP[i], workDP[i + 1]).square; + } + return sum; +} + +export function finishAggregationForIntegral(options: InternalHistoryOptions): void { + let preBorderValueRemoved = false; + let postBorderValueRemoved = false; + const originalResultLength = options.processing!.length; + const finalResult: IobDataEntry[] = []; + + // If timeIntervals are used, delegate to the specialized implementation. + if (options.timeIntervals) { + finishAggregationForIntegralEx(options); + return; + } + + if (!options.processing) { + return; + } + + // Iterate all processing slots (including pre/post border entries) + for (let k = 0; k < options.processing.length; k++) { + let indexStartTs: number; + let indexEndTs: number; + /*if (options.timeIntervals) { + if (k === 0) { + indexEndTs = options.timeIntervals as TimeInterval[][0].start; + indexStartTs = + options.timeIntervals[0].start - (options.timeIntervals[0].end - options.timeIntervals[0].start); + } else if (k >= options.timeIntervals.length) { + indexStartTs = options.timeIntervals[options.timeIntervals.length - 1].end; + indexEndTs = + indexStartTs + (indexStartTs - options.timeIntervals[options.timeIntervals.length - 1].start); + } else { + indexStartTs = options.timeIntervals[k - 1].start; + indexEndTs = options.timeIntervals[k - 1].end; + } + } else */ { + indexStartTs = options.start! + (k - 1) * options.step!; + indexEndTs = indexStartTs + options.step!; + } + + const len = options.integralDataPoints![k]?.length; + if (len) { + // Sort data points by ts first + options.integralDataPoints![k].sort(sortByTs); + } + + // Make sure that we have entries that always start at the beginning of the interval + if ( + (!len || options.integralDataPoints![k][0].ts > indexStartTs) && + options.integralDataPoints![k - 1] && + options.integralDataPoints![k - 1][options.integralDataPoints![k - 1].length - 1] + ) { + // if the first entry of this interval started somewhere in the start of the interval, add a start entry + // same if there is no entry at all in the timeframe, use last entry from interval before + options.integralDataPoints![k] = options.integralDataPoints![k] || []; + const time: IobDataEntry = { + ts: indexStartTs, + val: options.integralDataPoints![k - 1][options.integralDataPoints![k - 1].length - 1].val, + }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + options.integralDataPoints![k].unshift(time); + if (options.logDebug && options.log) { + options.log( + `Integral: ${k}: Added start entry for interval with ts=${indexStartTs}, val=${options.integralDataPoints![k][0].val}`, + ); + } + } else if (len && options.integralDataPoints![k][0].ts > indexStartTs) { + const time: IobDataEntry = { + ts: indexStartTs, + val: options.integralDataPoints![k][0].val, + }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + options.integralDataPoints![k].unshift(time); + if (options.logDebug && options.log) { + options.log( + `Integral: ${k}: Added start entry for interval with ts=${indexStartTs}, val=${options.integralDataPoints![k][0].val} with same value as first point in interval because no former datapoint was found`, + ); + } + } else if (len && options.integralDataPoints![k][0].ts < indexStartTs) { + // if the first entry of this interval started before the start of the interval, search for the last value before the start of the interval, add as start entry + let preFirstIndex = null; + for (let kk = 0; kk < options.integralDataPoints![k].length; kk++) { + if (options.integralDataPoints![k][kk].ts >= indexStartTs) { + break; + } + preFirstIndex = kk; + } + if (preFirstIndex !== null) { + const time: IobDataEntry = { + ts: indexStartTs, + val: options.integralDataPoints![k][preFirstIndex].val, + }; + if (options.logDebug && options.log) { + time.time = new Date(time.ts).toISOString(); + } + + options.integralDataPoints![k].splice(0, preFirstIndex, time); + if (options.logDebug && options.log) { + options.log( + `Integral: ${k}: Remove ${preFirstIndex + 1} entries and add start entry for interval with ts=${indexStartTs}, val=${options.integralDataPoints![k][0].val}`, + ); + } + } + } + + // get middle of the time interval + const ts: number = + options.processing[k] !== undefined && options.processing[k].val.ts + ? (options.processing[k].val.ts as number) + : Math.round(indexStartTs + (indexEndTs - indexStartTs) / 2); + + const point: IobDataEntry = { + ts, + val: null, + }; + + const integralDataPoints = options.integralDataPoints![k] || []; + if (options.logDebug && options.log) { + const vals = integralDataPoints.map(dp => `[${dp.ts}, ${dp.val}]`); + options.log( + `Integral: ${k}: ${integralDataPoints.length} data points for interval ${indexStartTs} - ${indexEndTs}: ${vals.join(',')}`, + ); + } + + // Calculate Intervals and always calculate till the interval end (start made sure above already) + for (let kk = 0; kk < integralDataPoints.length; kk++) { + // Determine the end timestamp for this segment: next point ts or interval end + const valEndTs = integralDataPoints[kk + 1] + ? Math.min(integralDataPoints[kk + 1].ts, indexEndTs) + : indexEndTs; + + // Ignore segments that don't belong or have zero duration + const valDuration = valEndTs - integralDataPoints[kk].ts; + if (valDuration < 0) { + if (options.logDebug && options.log) { + options.log( + `Integral: ${k}[${kk}] segment outside interval, ignore ${JSON.stringify(integralDataPoints[kk])} (vs. ${valEndTs})`, + ); + } + break; + } + if (valDuration === 0) { + if (options.logDebug && options.log) { + options.log( + `Integral: ${k}[${kk}] zero duration, ignore ${JSON.stringify(integralDataPoints[kk])}`, + ); + } + continue; + } + + // Read segment start and end values (treat null as 0) + let valStart = parseFloat(integralDataPoints[kk].val as unknown as string) || 0; + // End value is the next value, or if none, assume "linearity" + let valEnd = + parseFloat( + (integralDataPoints[kk + 1] + ? integralDataPoints[kk + 1].val + : options.integralDataPoints![k + 1] && options.integralDataPoints![k + 1][0] + ? options.integralDataPoints![k + 1][0].val + : valStart) as unknown as string, + ) || 0; + + // Accumulate integral according to interpolation mode + if (options.integralInterpolation !== 'linear' || valStart === valEnd) { + // Rectangle approximation: constant value = valStart + const integralAdd = (valStart * valDuration) / options.integralUnit!; + // simple rectangle linear interpolation + if (options.logDebug && options.log) { + options.log(`Integral: ${k}[${kk}] : Add ${integralAdd} from val=${valStart} for ${valDuration}`); + } + point.val! += integralAdd; + } else if ((valStart >= 0 && valEnd >= 0) || (valStart <= 0 && valEnd <= 0)) { + // Both values on same side of zero: rectangle + triangle decomposition + let multiplier = 1; + if (valStart <= 0 && valEnd <= 0) { + multiplier = -1; // correct the sign at the end + valStart = -valStart; + valEnd = -valEnd; + } + const minVal = Math.min(valStart, valEnd); + const maxVal = Math.max(valStart, valEnd); + const rectPart = (minVal * valDuration) / options.integralUnit!; + const trianglePart = ((maxVal - minVal) * valDuration * 0.5) / options.integralUnit!; + const integralAdd = (rectPart + trianglePart) * multiplier; + if (options.logDebug && options.log) { + options.log( + `Integral: ${k}[${kk}] : Add R${rectPart} + T${trianglePart} => ${integralAdd} from val=${valStart} to ${valEnd} for ${valDuration}`, + ); + } + point.val! += integralAdd; + } else { + // Values are on different sides of 0, so we need to find the 0 crossing + const zeroCrossing = Math.abs((valStart * valDuration) / (valEnd - valStart)); + // Then calculate two linear segments, one from 0 to the crossing, and one from the crossing to the end + const trianglePart1 = (valStart * zeroCrossing * 0.5) / options.integralUnit!; + const trianglePart2 = (valEnd * (valDuration - zeroCrossing) * 0.5) / options.integralUnit!; + const integralAdd = trianglePart1 + trianglePart2; + if (options.logDebug && options.log) { + options.log( + `Integral: ${k}[${kk}] : Add T${trianglePart1} + T${trianglePart2} => ${integralAdd} from val=${valStart} to ${valEnd} for ${valDuration} (zero crossing ${zeroCrossing})`, + ); + } + point.val! += integralAdd; + } + } + /* + options.processing[k] = { + ts: options.processing[k].val.ts, + val: options.processing[k].val.val + } + */ + // If we produced a numeric value, append to final result; otherwise track removed border flags + if (point.val !== null) { + finalResult.push(point); + } else if (k === 0) { + preBorderValueRemoved = true; + } else if (k === originalResultLength - 1) { + postBorderValueRemoved = true; + } + } + + // If requested, remove pre- / post-border values from the result + if (options.removeBorderValues) { + // we cut out the additional results + if (!preBorderValueRemoved) { + finalResult.splice(0, 1); + } + if (!postBorderValueRemoved) { + finalResult.length--; + } + } + + options.result = finalResult; +} + +function finishAggregationForMinMax(options: InternalHistoryOptions): void { + if (!options.processing) { + return; + } + + let preBorderValueRemoved = false; + let postBorderValueRemoved = false; + const originalResultLength = options.processing.length; + + const startIndex = 0; + const endIndex = options.processing.length; + const finalResult: IobDataEntry[] = []; + + for (let ii = startIndex; ii < endIndex; ii++) { + // it is no one value in this period + if (options.processing[ii] === undefined || options.processing[ii].start.ts === null) { + if (ii === 0) { + preBorderValueRemoved = true; + } else if (ii === originalResultLength - 1) { + postBorderValueRemoved = true; + } + // options.processing.splice(ii, 1); + continue; + } + // just one value in this period: max == min == start == end + if (options.processing[ii].start.ts === options.processing[ii].end.ts) { + finalResult.push({ + ts: options.processing[ii].start.ts as number, + val: options.processing[ii].start.val, + }); + } else if (options.processing[ii].min.ts === options.processing[ii].max.ts) { + // if just 2 values: start == min == max, end + if ( + options.processing[ii].start.ts === options.processing[ii].min.ts || + options.processing[ii].end.ts === options.processing[ii].min.ts + ) { + finalResult.push({ + ts: options.processing[ii].start.ts as number, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts as number, + val: options.processing[ii].end.val, + }); + } else { + // if just 3 values: start, min == max, end + finalResult.push({ + ts: options.processing[ii].start.ts as number, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].max.ts as number, + val: options.processing[ii].max.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts as number, + val: options.processing[ii].end.val, + }); + } + } else if (options.processing[ii].start.ts === options.processing[ii].max.ts) { + // just one value in this period: start == max, min == end + if (options.processing[ii].min.ts === options.processing[ii].end.ts) { + finalResult.push({ + ts: options.processing[ii].start.ts as number, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts as number, + val: options.processing[ii].end.val, + }); + } else { + // start == max, min, end + finalResult.push({ + ts: options.processing[ii].start.ts as number, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].min.ts as number, + val: options.processing[ii].min.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts as number, + val: options.processing[ii].end.val, + }); + } + } else if (options.processing[ii].end.ts === options.processing[ii].max.ts) { + // just one value in this period: start == min, max == end + if (options.processing[ii].min.ts === options.processing[ii].start.ts) { + finalResult.push({ + ts: options.processing[ii].start.ts as number, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts as number, + val: options.processing[ii].end.val, + }); + } else { + // start, min, max == end + finalResult.push({ + ts: options.processing[ii].start.ts as number, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].min.ts as number, + val: options.processing[ii].min.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts as number, + val: options.processing[ii].end.val, + }); + } + } else if ( + options.processing[ii].start.ts === options.processing[ii].min.ts || + options.processing[ii].end.ts === options.processing[ii].min.ts + ) { + // just one value in this period: start == min, max, end + finalResult.push({ + ts: options.processing[ii].start.ts as number, + val: options.processing[ii].start.val, + }); + finalResult.push({ + ts: options.processing[ii].max.ts as number, + val: options.processing[ii].max.val, + }); + finalResult.push({ + ts: options.processing[ii].end.ts as number, + val: options.processing[ii].end.val, + }); + } else { + finalResult.push({ + ts: options.processing[ii].start.ts as number, + val: options.processing[ii].start.val, + }); + // just one value in this period: start == min, max, end + if ((options.processing[ii].max.ts as number) > (options.processing[ii].min.ts as number)) { + finalResult.push({ + ts: options.processing[ii].min.ts as number, + val: options.processing[ii].min.val, + }); + finalResult.push({ + ts: options.processing[ii].max.ts as number, + val: options.processing[ii].max.val, + }); + } else { + finalResult.push({ + ts: options.processing[ii].max.ts as number, + val: options.processing[ii].max.val, + }); + finalResult.push({ + ts: options.processing[ii].min.ts as number, + val: options.processing[ii].min.val, + }); + } + finalResult.push({ + ts: options.processing[ii].end.ts as number, + val: options.processing[ii].end.val, + }); + } + } + + if (options.removeBorderValues) { + // we cut out the additional results + if (!preBorderValueRemoved) { + finalResult.splice(0, 1); + } + if (!postBorderValueRemoved) { + finalResult.length--; + } + } + options.result = finalResult; +} + +function finishAggregationForAverage(options: InternalHistoryOptions): void { + const round = options.round || 100; + let startIndex = 0; + if (!options.processing) { + return; + } + let endIndex = options.processing.length; + const finalResult: IobDataEntry[] = []; + if (options.removeBorderValues) { + // we cut out the additional results + // options.processing.splice(0, 1); + // options.averageCount.splice(0, 1); + // options.processing.length--; + // options.averageCount.length--; + startIndex++; + endIndex--; + } + for (let k = startIndex; k < endIndex; k++) { + if (options.processing[k] !== undefined && options.processing[k].val.ts) { + finalResult.push({ + ts: options.processing[k].val.ts as number, + val: + options.processing[k].val.val !== null + ? Math.round(((options.processing[k].val.val as number) / options.averageCount![k]) * round) / + round + : null, + }); + } else { + // no one value in this interval + // options.processing.splice(k, 1); + // options.averageCount.splice(k, 1); // not needed to clean up because not used anymore afterwards + } + } + options.result = finalResult; +} + +function finishAggregationForCount(options: InternalHistoryOptions): void { + let startIndex = 0; + if (!options.processing) { + return; + } + let endIndex = options.processing.length; + const finalResult: IobDataEntry[] = []; + if (options.removeBorderValues) { + // we cut out the additional results + // options.processing.splice(0, 1); + // options.averageCount.splice(0, 1); + // options.processing.length--; + // options.averageCount.length--; + startIndex++; + endIndex--; + } + for (let k = startIndex; k < endIndex; k++) { + if (options.processing[k] !== undefined && options.processing[k].val.ts) { + finalResult.push({ + ts: options.processing[k].val.ts as number, + val: options.averageCount![k], + }); + } else { + // no one value in this interval + // options.processing.splice(k, 1); + // options.averageCount.splice(k, 1); // not needed to clean up because not used anymore afterward + } + } + options.result = finalResult; +} + +export function finishAggregationPercentile(options: InternalHistoryOptions): void { + let startIndex = 0; + if (!options.processing) { + return; + } + let endIndex = options.processing.length; + const finalResult: IobDataEntry[] = []; + if (options.removeBorderValues) { + // we cut out the additional results + /* + options.processing.splice(0, 1); + options.quantileDataPoints.splice(0, 1); + options.processing.length-- + options.quantileDataPoints.length--; + */ + startIndex++; + endIndex--; + } + for (let k = startIndex; k < endIndex; k++) { + if (options.processing[k] !== undefined && options.processing[k].val.ts) { + const point: IobDataEntry = { + ts: options.processing[k].val.ts as number, + val: quantile(options.quantile, options.quantileDataPoints![k]), + }; + if (options.logDebug && options.log) { + options.log(`Quantile ${k} ${point.ts}: ${options.quantileDataPoints![k].join(', ')} -> ${point.val}`); + } + finalResult.push(point); + } else { + // no one value in this interval + // options.processing.splice(k, 1); + // options.quantileDataPoints.splice(k, 1); // not needed to clean up because not used anymore afterward + } + } + options.result = finalResult; +} + +function finishAggregationTotalIntegral(options: InternalHistoryOptions): void { + // calculate first entry + if (options.totalIntegralDataPoints?.[0] && options.totalIntegralDataPoints[0].ts !== options.start) { + if ( + options.totalIntegralDataPoints[0] && + options.totalIntegralDataPoints[0].ts < options.start! && + options.totalIntegralDataPoints[1] && + options.totalIntegralDataPoints[1].ts > options.start! + ) { + const y1 = options.totalIntegralDataPoints[0].val!; + const y2 = options.totalIntegralDataPoints[1].val!; + const x1 = options.totalIntegralDataPoints[0].ts; + const x2 = options.totalIntegralDataPoints[1].ts; + const val = y1 + ((y2 - y1) * (options.start! - x1)) / (x2 - x1); + options.totalIntegralDataPoints[0] = { + ts: options.start!, + val, + }; + } + } + + // calculate last entry + const len = options.totalIntegralDataPoints?.length || 0; + if ( + options.totalIntegralDataPoints && + options.totalIntegralDataPoints[len - 1] && + options.totalIntegralDataPoints[len - 1].ts !== options.end + ) { + if ( + options.totalIntegralDataPoints[len - 1] && + options.totalIntegralDataPoints[len - 1].ts > options.end! && + options.totalIntegralDataPoints[len - 2] && + options.totalIntegralDataPoints[len - 2].ts < options.end! + ) { + const y1 = options.totalIntegralDataPoints[len - 2].val as number; + const y2 = options.totalIntegralDataPoints[len - 1].val as number; + const x1 = options.totalIntegralDataPoints[len - 2].ts; + const x2 = options.totalIntegralDataPoints[len - 1].ts; + const val = y1 + ((y2 - y1) * (options.start! - x1)) / (x2 - x1); + options.totalIntegralDataPoints[len - 1] = { + ts: options.end as number, + val, + }; + } else if ( + options.totalIntegralDataPoints[len - 1] && + options.totalIntegralDataPoints[len - 1].ts < options.end! + ) { + // assume that now we have the same value as before + options.totalIntegralDataPoints.push({ + ts: options.end!, + val: options.totalIntegralDataPoints[len - 1].val, + }); + } + } + + let integral = 0; + if (options.totalIntegralDataPoints) { + for (let i = 1; i < options.totalIntegralDataPoints.length; i++) { + integral += calcDiff(options.totalIntegralDataPoints[i - 1], options.totalIntegralDataPoints[i]).square; + } + } + options.result = [ + { + ts: options.end!, + val: integral, + }, + ]; +} + +function finishAggregationForSimple(options: InternalHistoryOptions): void { + let startIndex = 0; + if (!options.processing) { + return; + } + let endIndex = options.processing.length; + const finalResult: IobDataEntry[] = []; + if (options.removeBorderValues) { + // we cut out the additional results + // options.processing.splice(0, 1); + // options.processing.length--; + startIndex++; + endIndex--; + } + for (let j = startIndex; j < endIndex; j++) { + if (options.processing[j] !== undefined && options.processing[j].val.ts) { + finalResult.push({ + ts: options.processing[j].val.ts as number, + val: options.processing[j].val.val, + }); + } else { + // no one value in this interval + // options.processing.splice(j, 1); + } + } + options.result = finalResult; +} + +export function finishAggregation(options: InternalHistoryOptions): void { + if (options.aggregate === 'minmax') { + finishAggregationForMinMax(options); + } else if (options.aggregate === 'average') { + finishAggregationForAverage(options); + } else if (options.aggregate === 'count') { + finishAggregationForCount(options); + } else if (options.aggregate === 'integral') { + finishAggregationForIntegral(options); + } else if (options.aggregate === 'percentile' || options.aggregate === 'quantile') { + finishAggregationPercentile(options); + } else if (options.aggregate === 'integralTotal') { + finishAggregationTotalIntegral(options); + } else { + finishAggregationForSimple(options); + } + // free memory + // @ts-expect-error ignore + options.processing = null; + + beautify(options); +} + +/** + * Beautify the result - remove null values, add start and end values if needed. + * Also round the values if requested and add ID to every value if requested. + */ +export function beautify(options: InternalHistoryOptions): void { + if (options.logDebug && options.log) { + options.log(`Beautify: ${options.result?.length} results`); + } + let preFirstValue = null; + let postLastValue = null; + + if ((options.ignoreNull as unknown as string) === 'true') { + // include nulls and replace them with last value + options.ignoreNull = true; + } else if ((options.ignoreNull as unknown as string) === 'false') { + // include nulls + options.ignoreNull = false; + } else if ((options.ignoreNull as unknown as string) === '0') { + // include nulls and replace them with 0 + options.ignoreNull = 0; + } else if (options.ignoreNull !== true && options.ignoreNull !== false && options.ignoreNull !== 0) { + options.ignoreNull = false; + } + + // process null values, remove points outside the span and find first points after end and before start + if (options.result) { + for (let i = 0; i < options.result.length; i++) { + if (options.ignoreNull !== false) { + // if value is null + if (options.result[i].val === null) { + // null value must be replaced with last not null value + if (options.ignoreNull === true) { + // remove value + options.result.splice(i, 1); + i--; + continue; + } else { + // null value must be replaced with 0 + options.result[i].val = options.ignoreNull; + } + } + } + + // remove all not requested points + if (options.result[i].ts < options.start!) { + preFirstValue = options.result[i].val !== null ? options.result[i] : null; + options.result.splice(i, 1); + i--; + continue; + } + + postLastValue = options.result[i].val !== null ? options.result[i] : null; + + if (options.result[i].ts > options.end!) { + options.result.splice(i, options.result.length - i); + break; + } + if (options.round && options.result[i].val && typeof options.result[i].val === 'number') { + options.result[i].val = Math.round(options.result[i].val! * options.round) / options.round; + } + } + } + + // check start and stop + if (options.result?.length && options.aggregate !== 'none' && !options.removeBorderValues) { + const firstTS = options.result[0].ts; + + if (firstTS > options.start! && !options.removeBorderValues) { + if (preFirstValue) { + const firstY = options.result[0].val; + // if steps + if (options.aggregate === 'onchange' || !options.aggregate) { + if (preFirstValue.ts !== firstTS) { + options.result.unshift({ ts: options.start!, val: preFirstValue.val }); + } else { + if (options.ignoreNull) { + options.result.unshift({ ts: options.start!, val: firstY }); + } + } + } else { + if (preFirstValue.ts !== firstTS) { + if (firstY !== null) { + // interpolate + const y = + preFirstValue.val! + + ((firstY - preFirstValue.val!) * (options.start! - preFirstValue.ts)) / + (firstTS - preFirstValue.ts); + options.result.unshift({ ts: options.start!, val: y, i: true }); + if (options.logDebug && options.log) { + options.log( + `interpolate ${y} from ${preFirstValue.val} to ${firstY} as first return value`, + ); + } + } else { + options.result.unshift({ ts: options.start!, val: null }); + } + } else { + if (options.ignoreNull) { + options.result.unshift({ ts: options.start!, val: firstY }); + } + } + } + } else { + if (options.ignoreNull) { + options.result.unshift({ ts: options.start!, val: options.result[0].val }); + } else { + options.result.unshift({ ts: options.start!, val: null }); + } + } + } + + const lastTS = options.result[options.result.length - 1].ts; + if (lastTS < options.end! && !options.removeBorderValues) { + if (postLastValue) { + // if steps + if (options.aggregate === 'onchange' || !options.aggregate) { + // if more data following, draw line to the end of the chart + if (postLastValue.ts !== lastTS) { + options.result.push({ ts: options.end!, val: postLastValue.val }); + } else { + if (options.ignoreNull) { + options.result.push({ ts: options.end!, val: postLastValue.val }); + } + } + } else { + if (postLastValue.ts !== lastTS) { + const lastY = options.result[options.result.length - 1].val; + if (lastY !== null) { + // make interpolation + const _y = + lastY + + ((postLastValue.val! - lastY) * (options.end! - lastTS)) / (postLastValue.ts - lastTS); + options.result.push({ ts: options.end!, val: _y, i: true }); + if (options.logDebug && options.log) { + options.log( + `interpolate ${_y} from ${lastY} to ${postLastValue.val} as last return value`, + ); + } + } else { + options.result.push({ ts: options.end!, val: null }); + } + } else { + if (options.ignoreNull) { + options.result.push({ ts: options.end!, val: postLastValue.val }); + } + } + } + } else { + if (options.ignoreNull) { + const lastY = options.result[options.result.length - 1].val; + // if no more data, that means do not draw line + options.result.push({ ts: options.end!, val: lastY }); + } else { + // if no more data, that means do not draw line + options.result.push({ ts: options.end!, val: null }); + } + } + } + } else if (options.aggregate === 'none') { + if (options.count && options.result && options.result.length > options.count) { + options.result.splice(0, options.result.length - options.count); + } + } + + if (options.addId && options.result && options.id) { + for (let i = 0; i < options.result.length; i++) { + if (!options.result[i].id) { + options.result[i].id = options.id; + } + } + } +} + +/** + * Sends the history to the caller + * + * @param adapter Adapter instance + * @param msg ioBroker message to respond to + * @param id State ID + * @param initialOptions get history options + * @param dataOrError Array or error string + * @param startTime Start time of the request just to measure duration + * @param logId Optional log ID to prefix log messages + */ +export function sendResponse( + adapter: ioBroker.Adapter, + msg: ioBroker.Message, + id: string | undefined, + initialOptions: GetHistoryOptions, + dataOrError: IobDataEntry[] | string, + startTime: number, + logId?: string, +): void { + if (typeof dataOrError === 'string') { + adapter.log.error(dataOrError); + + adapter.sendTo( + msg.from, + msg.command, + { + result: [], + step: 0, + error: dataOrError, + sessionId: initialOptions.sessionId, + }, + msg.callback, + ); + return; + } + + let log: ((text: string) => void) | undefined; + if (logId) { + log = (text: string): void => { + adapter.log.debug(`${logId}: ${text}`); + }; + } + + // We now know that dataOrError is IobDataEntry[] + const data = dataOrError; + + if (initialOptions.count && !initialOptions.start && data.length > initialOptions.count) { + data.splice(0, data.length - initialOptions.count); + } + if (data[0]) { + let result: IobDataEntry[]; + initialOptions.start ||= data[0].ts; + + let step = initialOptions.step || 0; + const sourceLength = data.length; + if (!initialOptions.aggregate || initialOptions.aggregate === 'none' || initialOptions.preAggregated) { + const options: InternalHistoryOptions = initAggregate(initialOptions, id, undefined, log); + options.result = data; + step = 0; + // convert ack from 0/1 to false/true + if (initialOptions.ack) { + for (let i = 0; i < data.length; i++) { + data[i].ack = !!data[i].ack; + } + } + beautify(options); + + if (options.aggregate === 'none' && options.count && options.result.length > options.count) { + options.result.splice(0, options.result.length - options.count); + } + result = options.result; + adapter.log.debug( + `Send with no aggregation: ${result.length} of: ${sourceLength} in: ${Date.now() - startTime}ms`, + ); + } else { + const options: InternalHistoryOptions = initAggregate(initialOptions, id, undefined, log); + aggregation(options, data); + finishAggregation(options); + result = options.result!; + adapter.log.debug( + `Send after aggregation: ${result.length} of: ${sourceLength} in: ${Date.now() - startTime}ms`, + ); + } + + adapter.sendTo( + msg.from, + msg.command, + { + result, + step, + sessionId: initialOptions.sessionId, + }, + msg.callback, + ); + } else { + adapter.log.info('No Data'); + adapter.sendTo( + msg.from, + msg.command, + { result: [], step: null, sessionId: initialOptions.sessionId }, + msg.callback, + ); + } +} + +export function sendResponseCounter( + adapter: ioBroker.Adapter, + msg: ioBroker.Message, + options: GetHistoryOptions, + dataOrError: IobDataEntry[] | string, +): void { + // data + // 1586713810000 100 + // 1586713810010 200 + // 1586713810040 500 + // 1586713810050 0 + // 1586713810090 400 + // 1586713810100 0 + // 1586713810110 100 + if (typeof dataOrError === 'string') { + adapter.log.error(dataOrError); + return adapter.sendTo( + msg.from, + msg.command, + { + result: [], + error: dataOrError, + sessionId: options.sessionId, + }, + msg.callback, + ); + } + const data: IobDataEntry[] = dataOrError; + + if (data[0] && data[1]) { + // first | start | afterFirst | ...... | last | end | afterLast + // 5 | | 8 | 9 | 1 | 3 | | 5 + // | 5+(8-5/tsDiff) | | 9 | 1 | | 3+(5-3/tsDiff) | + // (9 - 6.5) + (4 - 1) + + if (data[1].ts === options.start) { + data.splice(0, 1); + } + + if (data[0].ts < options.start! && data[0].val! > data[1].val!) { + data.splice(0, 1); + } + + // interpolate from first to start time + if (data[0].ts < options.start!) { + const val = + data[0].val! + + (data[1].val! - data[0].val!) * ((options.start! - data[0].ts) / (data[1].ts - data[0].ts)); + data.splice(0, 1); + data.unshift({ ts: options.start!, val, i: true }); + } + + if (data[data.length - 2] !== undefined && data[data.length - 2].ts === options.end) { + data.length--; + } + + const veryLast = data[data.length - 1]; + const beforeLast = data[data.length - 2]; + + // interpolate from end time to last + if (veryLast !== undefined && beforeLast !== undefined && options.end! < veryLast.ts) { + const val = + beforeLast.val! + + (veryLast.val! - beforeLast.val!) * ((options.end! - beforeLast.ts) / (veryLast.ts - beforeLast.ts)); + data.length--; + data.push({ ts: options.end!, val, i: true }); + } + + // at this point we expect [6.5, 9, 1, 4] + // at this point we expect [150, 200, 500, 0, 400, 0, 50] + let sum = 0; + if (data.length > 1) { + let val = data[data.length - 1].val!; + for (let i = data.length - 2; i >= 0; i--) { + if (data[i].val! < val) { + sum += val - data[i].val!; + } + val = data[i].val!; + } + } + + adapter.sendTo(msg.from, msg.command, { result: sum, sessionId: options.sessionId }, msg.callback); + } else { + adapter.log.info('No Data'); + adapter.sendTo(msg.from, msg.command, { result: 0, step: null, sessionId: options.sessionId }, msg.callback); + } +} + +/** + * Get quantile value from an array. + * + * @param q - quantile + * @param list - list of sorted values (ascending) + * @returns Quantile value + */ +function getQuantileValue(q: number | null | undefined, list: number[]): number { + if (!q) { + return list[0]; + } + + const index = list.length * q; + if (Number.isInteger(index)) { + // mean of two middle numbers + return (list[index - 1] + list[index]) / 2; + } + return list[Math.ceil(index - 1)]; +} + +/** + * Calculate quantile for given array of values. + * + * @template T + * @param q - quantile or a list of quantiles + * @param list - array of values + */ +function quantile(q: number | null | undefined, list: number[]): number { + list = list.slice().sort(function (a, b) { + a = Number.isNaN(a) ? Number.NEGATIVE_INFINITY : a; + b = Number.isNaN(b) ? Number.NEGATIVE_INFINITY : b; + + if (a > b) { + return 1; + } + if (a < b) { + return -1; + } + + return 0; + }); + + return getQuantileValue(q, list); +} + +/** Sort function for IobDataEntry by timestamp */ +export function sortByTs(a: IobDataEntry, b: IobDataEntry): number { + return a.ts - b.ts; +} diff --git a/src/lib/connection-factory.ts b/src/lib/connection-factory.ts new file mode 100644 index 00000000..7e03539e --- /dev/null +++ b/src/lib/connection-factory.ts @@ -0,0 +1,11 @@ +export type SQLConnection = any; + +export abstract class ConnectionFactory { + abstract openConnection(options: any, callback: (err: Error | null, connection?: SQLConnection) => void): void; + abstract closeConnection(connection: SQLConnection, callback?: (err?: Error | null) => void): void; + abstract execute( + connection: SQLConnection, + sql: string, + callback: (err: Error | null | undefined, results?: Array) => void, + ): void; +} diff --git a/src/lib/dockerManager.types.ts b/src/lib/dockerManager.types.ts new file mode 100644 index 00000000..75357a0e --- /dev/null +++ b/src/lib/dockerManager.types.ts @@ -0,0 +1,716 @@ +interface HealthConfig { + Test?: string[] | undefined; + Interval?: number | undefined; + Timeout?: number | undefined; + StartPeriod?: number | undefined; + StartInterval?: number | undefined; + Retries?: number | undefined; +} + +export type ImageName = string; +export type ContainerName = string; +export type DockerContainerInspect = { + Id: string; + Created: string; + Path: string; + Args: string[]; + Stats?: ContainerStats; + State: { + Status: 'created' | 'restarting' | 'running' | 'removing' | 'paused' | 'exited' | 'dead'; + Running: boolean; + Paused: boolean; + Restarting: boolean; + OOMKilled: boolean; + Dead: boolean; + Pid: number; + ExitCode: number; + Error: string; + StartedAt: string; + FinishedAt: string; + }; + Image: string; + ResolvConfPath: string; + HostnamePath: string; + HostsPath: string; + LogPath: string; + Name: string; + RestartCount: number; + Driver: string; + Platform: string; + MountLabel: string; + ProcessLabel: string; + AppArmorProfile: string; + ExecIDs: null | string[]; + HostConfig: { + Binds: string[]; + ContainerIDFile: string; + LogConfig: { + Type: 'json-file' | 'local' | 'syslog' | 'journald' | 'gelf' | 'fluentd' | 'awslogs' | 'splunk' | 'none'; + Config: Record; + }; + NetworkMode: 'bridge' | 'host' | 'none' | 'container'; + PortBindings: Record< + string, + Array<{ + HostIp: string; + HostPort: string; + }> + >; + RestartPolicy: { + Name: string; + MaximumRetryCount: number; + }; + AutoRemove: boolean; + VolumeDriver: string; + VolumesFrom: null | string[]; + ConsoleSize: [number, number]; + CapAdd: null | string[]; + CapDrop: null | string[]; + CgroupnsMode: string; + Dns: string[]; + DnsOptions: string[]; + DnsSearch: string[]; + ExtraHosts: null | string[]; + GroupAdd: null | string[]; + IpcMode: 'none' | 'host'; + Cgroup: string; + Links: null | string[]; + OomScoreAdj: number; + PidMode: 'host'; + Privileged: boolean; + PublishAllPorts: boolean; + ReadonlyRootfs: boolean; + SecurityOpt: null | string[]; + UTSMode: string; + UsernsMode: string; + ShmSize: number; + Runtime: string; + Isolation: string; + CpuShares: number; + Memory: number; + NanoCpus: number; + CgroupParent: string; + BlkioWeight: number; + BlkioWeightDevice: any[]; + BlkioDeviceReadBps: any[]; + BlkioDeviceWriteBps: any[]; + BlkioDeviceReadIOps: any[]; + BlkioDeviceWriteIOps: any[]; + CpuPeriod: number; + CpuQuota: number; + CpuRealtimePeriod: number; + CpuRealtimeRuntime: number; + CpusetCpus: string; + CpusetMems: string; + Devices: any[]; + DeviceCgroupRules: null | string[]; + DeviceRequests: null | any[]; + MemoryReservation: number; + MemorySwap: number; + MemorySwappiness: null | number; + OomKillDisable: null | boolean; + PidsLimit: null | number; + Ulimits: any[]; + CpuCount: number; + CpuPercent: number; + IOMaximumIOps: number; + IOMaximumBandwidth: number; + MaskedPaths: string[]; + ReadonlyPaths: string[]; + /** Sysctls (e.g. net.core.somaxconn=1024) */ + Sysctls?: Record; // --sysctl + Init?: boolean; + }; + GraphDriver: { + Data: { + ID: string; + LowerDir: string; + MergedDir: string; + UpperDir: string; + WorkDir: string; + }; + Name: string; + }; + Mounts: Array<{ + Type: 'bind' | 'volume' | 'tmpfs' | 'npipe'; + Source: string; + Destination: string; + Mode: string; + RW: boolean; + Propagation: string; + }>; + Config: { + Hostname: string; + Domainname: string; + User: string; + AttachStdin: boolean; + AttachStdout: boolean; + AttachStderr: boolean; + ExposedPorts: Record>; + Tty: boolean; + OpenStdin: boolean; + StdinOnce: boolean; + Env: string[]; + Cmd: null | string[]; + Image: string; + Volumes: null | Record; + WorkingDir: string; + Entrypoint: string[]; + OnBuild: null | string[]; + Labels: Record; + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + StopSignal?: 'SIGTERM' | 'SIGKILL' | string; // e.g. "SIGTERM" + /** --stop-timeout (seconds) */ + StopTimeout?: number; + }; + NetworkSettings: { + Bridge: string; + SandboxID: string; + SandboxKey: string; + Ports: Record< + string, + Array<{ + HostIp: string; + HostPort: string; + }> + >; + HairpinMode: boolean; + LinkLocalIPv6Address: string; + LinkLocalIPv6PrefixLen: number; + SecondaryIPAddresses: null | any[]; + SecondaryIPv6Addresses: null | any[]; + EndpointID: string; + Gateway: string; + GlobalIPv6Address: string; + GlobalIPv6PrefixLen: number; + IPAddress: string; + IPPrefixLen: number; + IPv6Gateway: string; + MacAddress: string; + Networks: Record< + string, + { + IPAMConfig: null; + Links: null; + Aliases: null; + MacAddress: string; + DriverOpts: null; + GwPriority: number; + NetworkID: string; + EndpointID: string; + Gateway: string; + IPAddress: string; + IPPrefixLen: number; + IPv6Gateway: string; + GlobalIPv6Address: string; + GlobalIPv6PrefixLen: number; + DNSNames: null; + } + >; + }; +}; + +export type DockerImageInspect = { + Id: string; + RepoTags: string[]; + RepoDigests: string[]; + Parent: string; + Comment: string; + Created: string; + DockerVersion: string; + Author: string; + Architecture: string; + Os: string; + Size: number; + GraphDriver: { + Data: { + LowerDir: string; + MergedDir: string; + UpperDir: string; + WorkDir: string; + }; + Name: string; + }; + RootFS: { + Type: string; + Layers?: string[]; + BaseLayer?: string | undefined; + }; + Metadata?: { + LastTagTime: string; + }; + Config: { + Hostname: string; + Cmd: null | string[]; + Entrypoint: string[]; + Env: string[]; + ExposedPorts: Record>; + Labels: null | Record; + OnBuild: null | string[]; + User: string; + Volumes: null | Record; + WorkingDir: string; + Domainname: string; + AttachStdin: boolean; + AttachStdout: boolean; + AttachStderr: boolean; + Tty: boolean; + OpenStdin: boolean; + StdinOnce: boolean; + ArgsEscaped: boolean; + Image: string; + Healthcheck?: HealthConfig | undefined; + }; +}; + +// Enums & Helper Types +export type Protocol = 'tcp' | 'udp' | 'sctp'; +// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents +export type NetworkMode = 'bridge' | 'host' | 'none' | 'container' | string; // with container: in networkContainer // custom network name +export type RestartPolicy = 'no' | 'always' | 'unless-stopped' | 'on-failure'; +export type LogDriver = + | 'json-file' + | 'local' + | 'syslog' + | 'journald' + | 'gelf' + | 'fluentd' + | 'awslogs' + | 'splunk' + | 'none'; + +export interface PortBinding { + /** Container port (required) */ + containerPort: number | string; + /** Host port (optional for publishAllPorts) */ + hostPort?: number | string; + /** Host IP (e.g. 127.0.0.1) */ + hostIP?: string; + protocol?: Protocol; // default tcp +} + +export type EnvVar = Record; + +export interface LabelMap { + [key: string]: string; +} + +export interface Ulimit { + name: + | 'core' + | 'cpu' + | 'data' + | 'fsize' + | 'locks' + | 'memlock' + | 'msgqueue' + | 'nice' + | 'nofile' + | 'nproc' + | 'rss' + | 'rtprio' + | 'rttime' + | 'sigpending' + | 'stack'; + soft?: number; + hard?: number; +} + +export interface Healthcheck { + /** Equivalent to --health-cmd / compose.healthcheck.test */ + test: string[] | ['CMD-SHELL', string] | ['CMD', ...string[]] | ['NONE']; + /** --health-interval / compose.healthcheck.interval (ms) */ + interval?: number; + /** --health-timeout / compose.healthcheck.timeout (ms) */ + timeout?: number; + /** --health-retries / compose.healthcheck.retries */ + retries?: number; + /** --health-start-period / compose.healthcheck.start_period (ms) */ + startPeriod?: number; +} + +export interface DeviceMapping { + /** host device path, e.g. /dev/video0 */ + hostPath: string; + /** container path, e.g. /dev/video0 */ + containerPath?: string; + /** cgroup permissions, e.g. "rwm" */ + permissions?: string; +} + +export interface VolumeMount { + /** bind | volume | tmpfs | npipe */ + type: 'bind' | 'volume' | 'tmpfs' | 'npipe' | 'image'; + /** host path or named volume */ + source?: string | true; + /** container path (mountpoint) */ + target: string; + /** read-only mount */ + readOnly?: boolean; + /** consistency hints (Docker Desktop/macOS) */ + consistency?: 'default' | 'consistent' | 'cached' | 'delegated'; + /** volume options (compose-like) */ + volumeOptions?: { + nocopy?: boolean; + labels?: LabelMap; + }; + /** bind options */ + bindOptions?: { + propagation?: 'rprivate' | 'private' | 'rshared' | 'shared' | 'rslave' | 'slave'; + selinux?: 'z' | 'Z'; + }; + /** tmpfs options */ + tmpfsOptions?: { + size?: number; // bytes + mode?: number; // e.g. 1777 + }; + /** ioBroker custom: if true, the folder will be copied into newly created volume */ + iobAutoCopyFrom?: string; + /** Copy files from host to volume even if volume is not empty */ + iobAutoCopyFromForce?: boolean; + /** If this folder should be "backup"ed by ioBroker */ + iobBackup?: boolean; +} + +export interface Resources { + /** --cpus (e.g. 0.5), --cpu-shares, --cpu-quota/period, --cpuset-cpus */ + cpus?: number; + cpuShares?: number; + cpuQuota?: number; + cpuPeriod?: number; + cpusetCpus?: string; // e.g. "0-2,4" + /** --memory, --memory-swap, --memory-reservation (bytes) */ + memory?: number; + memorySwap?: number; // -1 = unlimited + memoryReservation?: number; + /** --pids-limit */ + pidsLimit?: number; + /** NVIDIA GPUs: --gpus '"all"' or '"device=1,2"' */ + gpus?: 'all' | { count?: number; devices?: number[] | string[] }; + /** shm size (bytes), --shm-size */ + shmSize?: number; + /** read-only root FS, --read-only */ + readOnlyRootFilesystem?: boolean; +} + +export interface Logging { + driver?: LogDriver; // --log-driver + options?: Record; // --log-opt key=value +} + +export interface Security { + /** --privileged */ + privileged?: boolean; + /** --cap-add / --cap-drop */ + capAdd?: string[]; + capDrop?: string[]; + /** user namespace: --userns */ + usernsMode?: string; // e.g. "host", "private" + /** --ipc / --pid */ + ipc?: 'none' | 'host' | 'private'; + pid?: 'host' | ''; + /** SELinux labels (compose style) */ + selinuxLabels?: string[]; + /** seccomp profile path or "unconfined" */ + seccomp?: string; + /** apparmor profile */ + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + apparmor?: 'unconfined' | 'docker-default' | string; + /** device cgroup rules */ + deviceCgroupRules?: string[]; // e.g. "c 189:* rmw" + /** extra groups inside container */ + groupAdd?: (number | string)[]; + /** no-new-privileges: true | false */ + noNewPrivileges?: boolean; +} + +export interface DNSConfig { + /** --dns */ + servers?: string[]; + /** --dns-search */ + search?: string[]; + /** --dns-opt */ + options?: string[]; +} + +export interface NetworkAttachment { + /** target network name */ + name?: string; // compose "networks:" key or docker network name + /** aliases within that network */ + aliases?: string[]; + /** static IPs (IPv4/IPv6) */ + ipv4Address?: string; + ipv6Address?: string; + /** links (legacy) */ + links?: string[]; + /** driver options (compose) */ + driverOpts?: Record; +} + +export interface BuildConfig { + /** compose: build context */ + context: string; + dockerfile?: string; + target?: string; + args?: Record; + labels?: LabelMap; + cacheFrom?: string[]; + network?: string; // build-time network + shmSize?: string | number; + extraHosts?: string[]; // ["host:ip"] +} + +export interface Restart { + policy?: RestartPolicy; // --restart + /** only for on-failure */ + maxRetries?: number; +} + +export interface StopConfig { + /** --stop-signal */ + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + signal?: 'SIGTERM' | 'SIGKILL' | string; // e.g. "SIGTERM" + /** --stop-timeout (seconds) */ + gracePeriodSec?: number; +} + +export interface HostMapping { + /** "hostname:ip" or split fields */ + host: string; + ip: string; +} + +// The master container configuration you can use in your manager: +export interface ContainerConfig { + /** If false, container is not started, but still visible in the list */ + iobEnabled?: boolean; // ioBroker setting + + /** If true, container is stopped when adapter unloads */ + iobStopOnUnload?: boolean; // ioBroker setting + + /** If true, the image will be automatically updated by every start */ + iobAutoImageUpdate?: boolean; // ioBroker setting + + /** If true, container will be monitored and if fails restarted */ + iobMonitoringEnabled?: boolean; // ioBroker setting + + /** Image reference (repo:tag or ID). If omitted and build is set, the image comes from build */ + image: ImageName; + + /** Compose-style build (optional) */ + build?: BuildConfig; + + /** --name */ + name?: ContainerName; + + /** Command & Entrypoint */ + command?: string[] | string; // CMD override + entrypoint?: string[] | string; // --entrypoint + + /** User & Working dir */ + user?: string | number; // --user (uid[:gid] | name) + workdir?: string; // --workdir + + /** Hostname & domain */ + hostname?: string; // --hostname + domainname?: string; + macAddress?: string; + + /** Environment & Labels */ + environment?: EnvVar; // -e, --env-file merged by your tool if needed + envFile?: string[]; // compose-style + labels?: LabelMap; // --label + labelFile?: string[]; // less common + + /** TTY & STDIN */ + tty?: boolean; // -t + stdinOpen?: boolean; // -i + attachStdin?: boolean; + attachStdout?: boolean; + attachStderr?: boolean; + openStdin?: boolean; + + /** Detach & Auto-remove */ + detach?: boolean; // -d + // If true, container is removed after exit (cannot be used with restart policies) + removeOnExit?: boolean; // --rm + + /** Ports */ + publishAllPorts?: boolean; // -P + ports?: PortBinding[]; // -p + + /** Expose (container-only visibility) */ + expose?: (number | string)[]; + + /** Volumes & Mounts */ + mounts?: VolumeMount[]; // --mount (recommended) + volumes?: string[]; // legacy "-v" strings also acceptable if you want + + /** Devices */ + devices?: DeviceMapping[]; // --device + + /** Extra hosts (host:ip) */ + extraHosts?: HostMapping[] | string[]; // --add-host + + /** DNS */ + dns?: DNSConfig; + + /** Networks */ + networkMode?: NetworkMode | true; // --network + networkContainer?: string; // when networkMode === "container" + networks?: NetworkAttachment[]; // compose-style multiple networks + + /** Healthcheck */ + healthcheck?: Healthcheck; + + /** Restart */ + restart?: Restart; + + /** Resources / Limits */ + resources?: Resources; + + /** Logging */ + logging?: Logging; + + /** Security */ + security?: Security; + + /** Sysctls (e.g. net.core.somaxconn=1024) */ + sysctls?: Record; // --sysctl + + /** Depends on other containers (compose-style) */ + dependsOn?: + | string[] + | Record; + + /** Init process (tini), --init */ + init?: boolean; + + /** Working lifecycle */ + stop?: StopConfig; + + /** IPC shm mount as tmpfs paths: --tmpfs */ + tmpfs?: (string | { target: string; size?: number; mode?: number })[]; + + /** Read-only root plus per-path write exceptions (compose: read_only + tmpfs/mounts) */ + readOnly?: boolean; + + /** Timezone, locale (via env usually), but explicit here if your manager wants to enforce */ + timezone?: string; + + /** Custom arbitrary labels for your manager’s bookkeeping */ + __meta?: Record; +} + +export type SizeInfo = { + total: number; + reclaimable: number; + active: number; + size: number; +}; + +export type DiskUsage = { + images?: SizeInfo; + containers?: SizeInfo; + volumes?: SizeInfo; + buildCache?: SizeInfo; + total: { size: number; reclaimable: number }; +}; + +export type ContainerInfo = { + id: string; + image: ImageName; + command: string; + createdAt: string; + status: 'created' | 'restarting' | 'running' | 'removing' | 'paused' | 'exited' | 'dead'; + uptime: string; + ports: string; + names: string; + labels: { [key: string]: string }; + httpLinks?: { [ip: string]: string[] }; +}; + +export type ImageInfo = { + repository: string; + tag: string; + id: string; + createdSince: string; + size: number; +}; +export type VolumeDriver = + | 'local' + | 'tmpfs' + | 'nfs' + | 'cifs' + | 'sshfs' + | 'flocker' + | 'glusterfs' + | 'ceph' + | 'rexray' + | 'portworx'; +export type VolumeInfo = { + name: string; + driver: VolumeDriver; + volume: string; +}; + +export type NetworkDriver = 'bridge' | 'container' | 'host' | 'macvlan' | 'overlay'; +export type NetworkInfo = { + name: string; + id: string; + driver: NetworkDriver; + scope: string; +}; + +export type DockerImageTagsResponse = { + count: number; + next: string | null; + previous: string | null; + results: { + creator: number; + id: number; + images: { + architecture: string; + features: string; + variant: string | null; + digest: string; + os: string; + os_features: string; + os_version: string | null; + size: number; + status: string; + last_pulled: string; + last_pushed: string; + }[]; + last_updated: string; + last_updater: number; + last_updater_username: string; + name: string; + repository: number; + full_size: number; + v2: boolean; + tag_status: string; + tag_last_pulled: string; + tag_last_pushed: string; + media_type: string; + content_type: string; + digest: string; + }[]; +}; + +export interface ContainerStats { + cpu: number; + memUsed: number; + memMax: number; + netRead: number; + netWrite: number; + processes: number; + blockIoRead: number; + blockIoWrite: number; + ts: number; +} + +export interface ContainerStatus extends ContainerStats { + status: 'created' | 'restarting' | 'running' | 'removing' | 'paused' | 'exited' | 'dead' | 'unknown'; + statusTs: number; +} diff --git a/src/lib/mssql-client.ts b/src/lib/mssql-client.ts new file mode 100644 index 00000000..56856f52 --- /dev/null +++ b/src/lib/mssql-client.ts @@ -0,0 +1,69 @@ +import { ConnectionFactory } from './connection-factory'; +import SQLClient from './sql-client'; +import { SQLClientPool, type PoolConfig } from './sql-client-pool'; + +import type { ConnectionPool, IResult, Request, config as MSSQLOptions } from 'mssql'; + +export type { MSSQLOptions }; + +class MSSQLConnectionFactory extends ConnectionFactory { + private ConnectionPool: typeof ConnectionPool | undefined; + private Request: typeof Request | undefined; + + openConnection(options: MSSQLOptions, callback: (err: Error | null, connection?: ConnectionPool) => void): void { + if (!this.ConnectionPool) { + void import('mssql').then(mssql => { + this.ConnectionPool = mssql.default.ConnectionPool; + this.Request = mssql.default.Request; + this.openConnection(options, callback); + }); + return; + } + const pos = options.server.indexOf(':'); + if (pos !== -1) { + options.port = parseInt(options.server.substring(pos + 1), 10); + options.server = options.server.substring(0, pos); + } + options.pool ||= {}; + options.pool.min = 0; + options.pool.max = 1; + + const connection = new this.ConnectionPool(options, (err?: string | null): void => { + if (err) { + callback(new Error(err)); + } else { + callback(null, connection); + } + }); + } + + closeConnection(connection: ConnectionPool, callback: (err?: Error | null) => void): void { + connection.close(callback); + } + + execute( + connection: ConnectionPool, + sql: string, + callback: (err: Error | null | undefined, result?: Array) => void, + ): void { + if (!this.Request) { + throw new Error('Not initialized request'); + } + const request = new this.Request(connection); + request.query(sql, (err?: Error | null, result?: IResult) => { + callback(err, result?.recordset); + }); + } +} + +export class MSSQLClient extends SQLClient { + constructor(options: MSSQLOptions) { + super(options, new MSSQLConnectionFactory()); + } +} + +export class MSSQLClientPool extends SQLClientPool { + constructor(poolOptions: PoolConfig, sqlOptions: MSSQLOptions) { + super(poolOptions, sqlOptions, new MSSQLConnectionFactory()); + } +} diff --git a/src/lib/mssql.ts b/src/lib/mssql.ts new file mode 100644 index 00000000..dc31d443 --- /dev/null +++ b/src/lib/mssql.ts @@ -0,0 +1,311 @@ +import type { TableName } from '../types'; + +export function init(dbName: string, doNotCreateDatabase?: boolean): string[] { + const commands = [ + `CREATE TABLE ${dbName}.dbo.sources (id INTEGER NOT NULL PRIMARY KEY IDENTITY(1,1), name varchar(255));`, + `CREATE TABLE ${dbName}.dbo.datapoints (id INTEGER NOT NULL PRIMARY KEY IDENTITY(1,1), name varchar(255), type INTEGER);`, + `CREATE TABLE ${dbName}.dbo.ts_number (id INTEGER, ts BIGINT, val REAL, ack BIT, _from INTEGER, q INTEGER);`, + `CREATE INDEX i_id on ${dbName}.dbo.ts_number (id, ts);`, + `CREATE TABLE ${dbName}.dbo.ts_string (id INTEGER, ts BIGINT, val TEXT, ack BIT, _from INTEGER, q INTEGER);`, + `CREATE INDEX i_id on ${dbName}.dbo.ts_string (id, ts);`, + `CREATE TABLE ${dbName}.dbo.ts_bool (id INTEGER, ts BIGINT, val BIT, ack BIT, _from INTEGER, q INTEGER);`, + `CREATE INDEX i_id on ${dbName}.dbo.ts_bool (id, ts);`, + `CREATE TABLE ${dbName}.dbo.ts_counter (id INTEGER, ts BIGINT, val REAL);`, + `CREATE INDEX i_id on ${dbName}.dbo.ts_counter (id, ts);`, + ]; + if (!doNotCreateDatabase) { + commands.unshift(`CREATE DATABASE ${dbName};`); + } + + return commands; +} + +export function destroy(dbName: string): string[] { + return [ + `DROP TABLE ${dbName}.dbo.ts_counter;`, + `DROP TABLE ${dbName}.dbo.ts_number;`, + `DROP TABLE ${dbName}.dbo.ts_string;`, + `DROP TABLE ${dbName}.dbo.ts_bool;`, + `DROP TABLE ${dbName}.dbo.sources;`, + `DROP TABLE ${dbName}.dbo.datapoints;`, + `DROP DATABASE ${dbName};`, + `DBCC FREEPROCCACHE;`, + ]; +} + +export function getFirstTs(dbName: string, table: TableName): string { + return `SELECT id, MIN(ts) AS ts FROM ${dbName}.dbo.${table} GROUP BY id;`; +} + +export function insert( + dbName: string, + index: number, + values: { + table: TableName; + state: { val: any; ts: number; ack?: boolean; q?: number }; + from?: number; + }[], +): string { + const insertValues: { [table: string]: string[] } = {}; + values.forEach(value => { + // state, from, db + insertValues[value.table] ||= []; + + if (!value.state || value.state.val === null || value.state.val === undefined) { + value.state.val = 'NULL'; + } else if (value.table === 'ts_string') { + value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; + } else if (value.table === 'ts_bool') { + value.state.val = value.state.val ? 1 : 0; + } else if (value.table === 'ts_number') { + if (isNaN(value.state.val)) { + value.state.val = 'NULL'; + } + } + + if (value.table === 'ts_counter') { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val})`); + } else { + insertValues[value.table].push( + `(${index}, ${value.state.ts}, ${value.state.val}, ${value.state.ack ? 1 : 0}, ${value.from || 0}, ${value.state.q || 0})`, + ); + } + }); + + const query: string[] = []; + for (const table in insertValues) { + if (table === 'ts_counter') { + while (insertValues[table].length) { + query.push( + `INSERT INTO ${dbName}.dbo.ts_counter (id, ts, val) VALUES ${insertValues[table].splice(0, 500).join(',')};`, + ); + } + } else { + while (insertValues[table].length) { + query.push( + `INSERT INTO ${dbName}.dbo.${table} (id, ts, val, ack, _from, q) VALUES ${insertValues[table].splice(0, 500).join(',')};`, + ); + } + } + } + + return query.join(' '); +} + +export function retention(dbName: string, index: number, table: TableName, retention: number): string { + const d = new Date(); + d.setSeconds(-retention); + let query = `DELETE FROM ${dbName}.dbo.${table} WHERE`; + query += ` id=${index}`; + query += ` AND ts < ${d.getTime()}`; + query += ';'; + return query; +} + +export function getIdSelect(dbName: string, name?: string): string { + if (!name) { + return `SELECT id, type, name FROM ${dbName}.dbo.datapoints;`; + } + return `SELECT id, type, name FROM ${dbName}.dbo.datapoints WHERE name='${name}';`; +} + +export function getIdInsert(dbName: string, name: string, type: 0 | 1 | 2): string { + return `INSERT INTO ${dbName}.dbo.datapoints (name, type) VALUES('${name}', ${type});`; +} + +export function getIdUpdate(dbName: string, id: number, type: 0 | 1 | 2): string { + return `UPDATE ${dbName}.dbo.datapoints SET type=${type} WHERE id=${id};`; +} + +export function getFromSelect(dbName: string, name?: string): string { + if (name) { + return `SELECT id FROM ${dbName}.dbo.sources WHERE name='${name}';`; + } + return `SELECT id, name FROM ${dbName}.dbo.sources;`; +} + +export function getFromInsert(dbName: string, values: string): string { + return `INSERT INTO ${dbName}.dbo.sources (name) VALUES('${values}');`; +} + +export function getCounterDiff( + dbName: string, + options: { + index: number; + start: number; + end: number; + }, +): string { + // Take first real value after start + const subQueryStart = `SELECT TOP 1 val, ts FROM ${dbName}.dbo.ts_number WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts>=${options.start} AND ${dbName}.dbo.ts_number.ts<${options.end} AND ${dbName}.dbo.ts_number.val IS NOT NULL ORDER BY ${dbName}.dbo.ts_number.ts ASC`; + // Take last real value before the end + const subQueryEnd = `SELECT TOP 1 val, ts FROM ${dbName}.dbo.ts_number WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts>=${options.start} AND ${dbName}.dbo.ts_number.ts<${options.end} AND ${dbName}.dbo.ts_number.val IS NOT NULL ORDER BY ${dbName}.dbo.ts_number.ts DESC`; + // Take last value before start + const subQueryFirst = `SELECT TOP 1 val, ts FROM ${dbName}.dbo.ts_number WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts< ${options.start} ORDER BY ${dbName}.dbo.ts_number.ts DESC`; + // Take next value after end + const subQueryLast = `SELECT TOP 1 val, ts FROM ${dbName}.dbo.ts_number WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts>=${options.end} ORDER BY ${dbName}.dbo.ts_number.ts ASC`; + // get values from counters where counter values changed + const subQueryCounterChanges = `SELECT val, ts FROM ${dbName}.dbo.ts_counter WHERE ${dbName}.dbo.ts_number.id=${options.index} AND ${dbName}.dbo.ts_number.ts>${options.start} AND ${dbName}.dbo.ts_number.ts<${options.end} AND ${dbName}.dbo.ts_number.val IS NOT NULL ORDER BY ${dbName}.dbo.ts_number.ts ASC`; + + return ( + `${subQueryFirst} ` + + `UNION ALL (${subQueryStart}) a ` + + `UNION ALL (${subQueryEnd}) b ` + + `UNION ALL (${subQueryLast}) c` + + `UNION ALL (${subQueryCounterChanges}) d` + ); +} + +export function getHistory( + dbName: string, + table: string, + options: ioBroker.GetHistoryOptions & { index: number | null }, +): string { + let query = 'SELECT * FROM (SELECT '; + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + query += ` TOP ${options.count}`; + } + query += ` ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? `, ack` : ''}${ + options.from ? `, ${dbName}.dbo.sources.name as 'from'` : '' + }${options.q ? `, q` : ''} FROM ${dbName}.dbo.${table}`; + + if (options.from) { + query += ` INNER JOIN ${dbName}.dbo.sources ON ${dbName}.dbo.sources.id=${dbName}.dbo.${table}._from`; + } + + let where = ''; + + if (options.index !== null) { + where += ` ${dbName}.dbo.${table}.id=${options.index}`; + } + if (options.end) { + where += `${where ? ` AND` : ''} ${dbName}.dbo.${table}.ts < ${options.end}`; + } + if (options.start) { + where += `${where ? ` AND` : ''} ${dbName}.dbo.${table}.ts >= ${options.start}`; + } + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + where += ` ORDER BY ts`; + + if ( + (!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries) + ) { + where += ` DESC`; + } else { + where += ` ASC`; + } + } + where += `) AS t`; + if (options.start) { + // add last value before start + let subQuery; + let subWhere; + subQuery = ` SELECT TOP 1 ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? `, ack` : ''}${ + options.from ? `, ${dbName}.dbo.sources.name as 'from'` : '' + }${options.q ? `, q` : ''} FROM ${dbName}.dbo.${table}`; + if (options.from) { + subQuery += ` INNER JOIN ${dbName}.dbo.sources ON ${dbName}.dbo.sources.id=${dbName}.dbo.${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${dbName}.dbo.${table}.id=${options.index}`; + } + if (options.ignoreNull) { + //subWhere += (subWhere ? ` AND` : '') + ` val <> NULL`; + } + subWhere += `${subWhere ? ` AND` : ''} ${dbName}.dbo.${table}.ts < ${options.start}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${dbName}.dbo.${table}.ts DESC`; + where += ` UNION ALL SELECT * FROM (${subQuery}) a`; + + // add next value after end + subQuery = ` SELECT TOP 1 ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? `, ack` : ''}${ + options.from ? `, ${dbName}.dbo.sources.name as 'from'` : '' + }${options.q ? `, q` : ''} FROM ${dbName}.dbo.${table}`; + if (options.from) { + subQuery += ` INNER JOIN ${dbName}.dbo.sources ON ${dbName}.dbo.sources.id=${dbName}.dbo.${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${dbName}.dbo.${table}.id=${options.index}`; + } + if (options.ignoreNull) { + //subWhere += (subWhere ? ` AND` : '') + ` val <> NULL`; + } + subWhere += `${subWhere ? ` AND` : ''} ${dbName}.dbo.${table}.ts >= ${options.end}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${dbName}.dbo.${table}.ts ASC`; + where += ` UNION ALL SELECT * FROM (${subQuery}) b`; + } + + if (where) { + query += ` WHERE ${where}`; + } + + query += ` ORDER BY ts`; + if ( + (!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries) + ) { + query += ` DESC`; + } else { + query += ` ASC`; + } + query += `;`; + + return query; +} + +export function deleteFromTable(dbName: string, table: TableName, index: number, start?: number, end?: number): string { + let query = `DELETE FROM ${dbName}.dbo.${table} WHERE`; + query += ` id=${index}`; + if (start && end) { + query += ` AND ${dbName}.dbo.${table}.ts>=${start} AND ${dbName}.dbo.${table}.ts<=${end}`; + } else if (start) { + query += ` AND ${dbName}.dbo.${table}.ts=${start}`; + } + + query += ';'; + + return query; +} + +export function update( + dbName: string, + index: number, + state: { val: number | string | boolean | null | undefined; ts: number; q?: number; ack?: boolean }, + from: number, + table: TableName, +): string { + if (!state || state.val === null || state.val === undefined) { + state.val = 'NULL'; + } else if (table === 'ts_bool') { + state.val = state.val ? 1 : 0; + } else if (table === 'ts_string') { + state.val = `'${state.val.toString().replace(/'/g, '')}'`; + } + + let query = `UPDATE ${dbName}.dbo.${table} SET `; + const vals: string[] = []; + if (state.val !== undefined) { + vals.push(`val=${state.val}`); + } + if (state.q !== undefined) { + vals.push(`q=${state.q}`); + } + if (from !== undefined) { + vals.push(`_from=${from}`); + } + if (state.ack !== undefined) { + vals.push(`ack=${state.ack ? 1 : 0}`); + } + query += vals.join(', '); + query += ` WHERE id=${index} AND ts=${state.ts};`; + + return query; +} diff --git a/src/lib/mysql-client.ts b/src/lib/mysql-client.ts new file mode 100644 index 00000000..dc26b53b --- /dev/null +++ b/src/lib/mysql-client.ts @@ -0,0 +1,56 @@ +import { ConnectionFactory, type SQLConnection } from './connection-factory'; +import SQLClient from './sql-client'; +import { SQLClientPool, type PoolConfig } from './sql-client-pool'; + +import type { Connection, ConnectionOptions as MySQLOptions } from 'mysql2'; + +export type { MySQLOptions }; + +class MySQL2ConnectionFactory extends ConnectionFactory { + private createConnection: any; + openConnection(options: MySQLOptions, callback: (err: Error | null, connection: Connection) => void): void { + if (!this.createConnection) { + void import('mysql2').then(mysql2 => { + this.createConnection = mysql2.default.createConnection; + this.openConnection(options, callback); + }); + return; + } + + const connection = this.createConnection(options); + connection.connect((err: Error | null): void => callback(err, connection)); + } + + closeConnection(connection: Connection | null | undefined, callback: (error?: Error | null) => void): void { + if (connection) { + connection.end(callback); + } else { + callback?.(null); + } + } + + execute( + connection: SQLConnection, + sql: string, + callback: (err: Error | null | undefined, results?: Array) => void, + ): void { + connection.execute(sql, (err: Error | null | undefined, results: Array) => { + if (err) { + return callback(err); + } + return callback(null, results); + }); + } +} + +export class MySQL2Client extends SQLClient { + constructor(sqlConnection: MySQLOptions) { + super(sqlConnection, new MySQL2ConnectionFactory()); + } +} + +export class MySQL2ClientPool extends SQLClientPool { + constructor(poolOptions: PoolConfig, sqlOptions: MySQLOptions) { + super(poolOptions, sqlOptions, new MySQL2ConnectionFactory()); + } +} diff --git a/src/lib/mysql.ts b/src/lib/mysql.ts new file mode 100644 index 00000000..0216fd84 --- /dev/null +++ b/src/lib/mysql.ts @@ -0,0 +1,304 @@ +import type { TableName } from '../types'; + +export function init(dbName: string, doNotCreateDatabase?: boolean): string[] { + const commands = [ + `CREATE TABLE \`${dbName}\`.sources (id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, name TEXT);`, + `CREATE TABLE \`${dbName}\`.datapoints (id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, name TEXT, type INTEGER);`, + `CREATE TABLE \`${dbName}\`.ts_number (id INTEGER, ts BIGINT, val REAL, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));`, + `CREATE TABLE \`${dbName}\`.ts_string (id INTEGER, ts BIGINT, val TEXT, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));`, + `CREATE TABLE \`${dbName}\`.ts_bool (id INTEGER, ts BIGINT, val BOOLEAN, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));`, + `CREATE TABLE \`${dbName}\`.ts_counter (id INTEGER, ts BIGINT, val REAL);`, + ]; + + !doNotCreateDatabase && + commands.unshift(`CREATE DATABASE \`${dbName}\` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;`); + + return commands; +} + +export function destroy(dbName: string): string[] { + return [ + `DROP TABLE \`${dbName}\`.ts_counter;`, + `DROP TABLE \`${dbName}\`.ts_number;`, + `DROP TABLE \`${dbName}\`.ts_string;`, + `DROP TABLE \`${dbName}\`.ts_bool;`, + `DROP TABLE \`${dbName}\`.sources;`, + `DROP TABLE \`${dbName}\`.datapoints;`, + `DROP DATABASE \`${dbName}\`;`, + ]; +} + +export function getFirstTs(dbName: string, table: TableName): string { + return `SELECT id, MIN(ts) AS ts FROM \`${dbName}\`.${table} GROUP BY id;`; +} + +export function insert( + dbName: string, + index: number, + values: { + table: TableName; + state: { val: any; ts: number; ack?: boolean; q?: number }; + from?: number; + }[], +): string { + const insertValues: { [table: string]: string[] } = {}; + values.forEach(value => { + // state, from, db + insertValues[value.table] = insertValues[value.table] || []; + + if (!value.state || value.state.val === null || value.state.val === undefined) { + value.state.val = 'NULL'; + } else if (value.table === 'ts_string') { + value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; + } else if (value.table === 'ts_number') { + if (isNaN(value.state.val)) { + value.state.val = 'NULL'; + } + } + + if (value.table === 'ts_counter') { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val})`); + } else { + insertValues[value.table].push( + `(${index}, ${value.state.ts}, ${value.state.val}, ${value.state.ack ? 1 : 0}, ${value.from || 0}, ${value.state.q || 0})`, + ); + } + }); + + const query: string[] = []; + for (const table in insertValues) { + if (table === 'ts_counter') { + while (insertValues[table].length) { + query.push( + `INSERT INTO \`${dbName}\`.ts_counter (id, ts, val) VALUES ${insertValues[table].splice(0, 500).join(',')};`, + ); + } + } else { + while (insertValues[table].length) { + query.push( + `INSERT INTO \`${dbName}\`.${table} (id, ts, val, ack, _from, q) VALUES ${insertValues[table].splice(0, 500).join(',')};`, + ); + } + } + } + + return query.join(' '); +} + +export function retention(dbName: string, index: number, table: TableName, retention: number): string { + const d = new Date(); + d.setSeconds(-retention); + let query = `DELETE FROM \`${dbName}\`.${table} WHERE`; + query += ` id=${index}`; + query += ` AND ts < ${d.getTime()}`; + query += ';'; + + return query; +} + +export function getIdSelect(dbName: string, name?: string): string { + if (!name) { + return `SELECT id, type, name FROM \`${dbName}\`.datapoints;`; + } + return `SELECT id, type, name FROM \`${dbName}\`.datapoints WHERE name='${name}';`; +} + +export function getIdInsert(dbName: string, name: string, type: 0 | 1 | 2): string { + return `INSERT INTO \`${dbName}\`.datapoints (name, type) VALUES('${name}', ${type});`; +} + +export function getIdUpdate(dbName: string, id: number, type: 0 | 1 | 2): string { + return `UPDATE \`${dbName}\`.datapoints SET type=${type} WHERE id=${id};`; +} + +export function getFromSelect(dbName: string, name?: string): string { + if (name) { + return `SELECT id FROM \`${dbName}\`.sources WHERE name='${name}';`; + } + return `SELECT id, name FROM \`${dbName}\`.sources;`; +} + +export function getFromInsert(dbName: string, values: string): string { + return `INSERT INTO \`${dbName}\`.sources (name) VALUES('${values}');`; +} + +export function getCounterDiff( + dbName: string, + options: { + index: number; + start: number; + end: number; + }, +): string { + // Take first real value after start + const subQueryStart = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>=${ + options.start + } AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC LIMIT 1`; + // Take last real value before the end + const subQueryEnd = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>=${ + options.start + } AND ts<${options.end} AND val IS NOT NULL ORDER BY ts DESC LIMIT 1`; + // Take last value before start + const subQueryFirst = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts< ${ + options.start + } ORDER BY ts DESC LIMIT 1`; + // Take next value after end + const subQueryLast = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>= ${ + options.end + } ORDER BY ts ASC LIMIT 1`; + // get values from counters where counter changed from up to down (e.g. counter changed) + const subQueryCounterChanges = `SELECT ts, val FROM \`${dbName}\`.ts_counter WHERE id=${options.index} AND ts>${ + options.start + } AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC`; + + return ( + `SELECT DISTINCT(a.ts), a.val from ((${subQueryFirst})\n` + + `UNION ALL \n(${subQueryStart})\n` + + `UNION ALL \n(${subQueryEnd})\n` + + `UNION ALL \n(${subQueryLast})\n` + + `UNION ALL \n(${subQueryCounterChanges})\n` + + `ORDER BY ts) a;` + ); +} + +export function getHistory( + dbName: string, + table: string, + options: ioBroker.GetHistoryOptions & { index: number | null }, +): string { + let query = `SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${ + options.from ? `, \`${dbName}\`.sources.name as 'from'` : '' + }${options.q ? ', q' : ''} FROM \`${dbName}\`.${table}`; + + if (options.from) { + query += ` INNER JOIN \`${dbName}\`.sources ON \`${dbName}\`.sources.id=\`${dbName}\`.${table}._from`; + } + + let where = ''; + + if (options.index !== null) { + where += ` \`${dbName}\`.${table}.id=${options.index}`; + } + if (options.end) { + where += `${where ? ' AND' : ''} \`${dbName}\`.${table}.ts < ${options.end}`; + } + if (options.start) { + where += `${where ? ' AND' : ''} \`${dbName}\`.${table}.ts >= ${options.start}`; + + let subQuery; + let subWhere; + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${ + options.from ? `, \`${dbName}\`.sources.name as 'from'` : '' + }${options.q ? ', q' : ''} FROM \`${dbName}\`.${table}`; + if (options.from) { + subQuery += ` INNER JOIN \`${dbName}\`.sources ON \`${dbName}\`.sources.id=\`${dbName}\`.${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` \`${dbName}\`.${table}.id=${options.index}`; + } + if (options.ignoreNull) { + // subWhere += (subWhere ? " AND" : "") + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} \`${dbName}\`.${table}.ts < ${options.start}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY \`${dbName}\`.${table}.ts DESC LIMIT 1`; + where += ` UNION ALL (${subQuery})`; + + // add next value after end + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${ + options.from ? `, \`${dbName}\`.sources.name as 'from'` : '' + }${options.q ? ', q' : ''} FROM \`${dbName}\`.${table}`; + if (options.from) { + subQuery += ` INNER JOIN \`${dbName}\`.sources ON \`${dbName}\`.sources.id=\`${dbName}\`.${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` \`${dbName}\`.${table}.id=${options.index}`; + } + if (options.ignoreNull) { + // subWhere += (subWhere ? " AND" : "") + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} \`${dbName}\`.${table}.ts >= ${options.end}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY \`${dbName}\`.${table}.ts ASC LIMIT 1`; + where += ` UNION ALL (${subQuery})`; + } + + if (where) { + query += ` WHERE ${where}`; + } + + query += ' ORDER BY ts'; + + if ( + (!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries) + ) { + query += ' DESC'; + } else { + query += ' ASC'; + } + + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + query += ` LIMIT ${options.count + 2}`; + } + + query += ';'; + return query; +} + +export function deleteFromTable(dbName: string, table: TableName, index: number, start?: number, end?: number): string { + let query = `DELETE FROM \`${dbName}\`.${table} WHERE`; + query += ` id=${index}`; + + if (start && end) { + query += ` AND ts>=${start} AND ts<=${end}`; + } else if (start) { + query += ` AND ts=${start}`; + } + + query += ';'; + + return query; +} + +export function update( + dbName: string, + index: number, + state: { val: number | string | boolean | null | undefined; ts: number; q?: number; ack?: boolean }, + from: number, + table: 'ts_bool' | 'ts_number' | 'ts_string' | 'ts_counter', +): string { + if (!state || state.val === null || state.val === undefined) { + state.val = 'NULL'; + } else if (table === 'ts_string') { + state.val = `'${state.val.toString().replace(/'/g, '')}'`; + } + + let query = `UPDATE \`${dbName}\`.${table} SET `; + const vals = []; + if (state.val !== undefined) { + vals.push(`val=${state.val}`); + } + if (state.q !== undefined) { + vals.push(`q=${state.q}`); + } + if (from !== undefined) { + vals.push(`_from=${from}`); + } + if (state.ack !== undefined) { + vals.push(`ack=${state.ack ? 1 : 0}`); + } + query += vals.join(', '); + query += ' WHERE '; + query += ` id=${index}`; + query += ` AND ts=${state.ts}`; + query += ';'; + + return query; +} diff --git a/src/lib/postgresql-client.ts b/src/lib/postgresql-client.ts new file mode 100644 index 00000000..88d43163 --- /dev/null +++ b/src/lib/postgresql-client.ts @@ -0,0 +1,57 @@ +import { ConnectionFactory } from './connection-factory'; +import SQLClient from './sql-client'; +import { SQLClientPool, type PoolConfig } from './sql-client-pool'; + +import type { Client, QueryResult, ClientConfig as PostgreSQLOptions } from 'pg'; + +export type { PostgreSQLOptions }; + +// PostgreSQLConnectionFactory does not use any of node-pg's built-in pooling. +class PostgreSQLConnectionFactory extends ConnectionFactory { + private Client: typeof Client | undefined; + + openConnection(connectString: PostgreSQLOptions, callback: (err: Error | null, connection: Client) => void): void { + if (!this.Client) { + void import('pg').then(pg => { + this.Client = pg.default.native?.Client || pg.default.Client; + this.openConnection(connectString, callback); + }); + return; + } + const connection = new this.Client(connectString); + connection.connect(err => callback(err, connection)); + } + + closeConnection(connection: Client | null | undefined, callback: (error?: Error | null) => void): void { + if (connection) { + connection.end(callback); + } else { + callback?.(null); + } + } + + execute( + connection: Client, + sql: string, + callback: (err: Error | null | undefined, results?: Array) => void, + ): void { + connection.query(sql, (err: Error, results: QueryResult): void => { + if (err) { + return callback(err); + } + return callback(null, results?.rows); + }); + } +} + +export class PostgreSQLClient extends SQLClient { + constructor(connectString: PostgreSQLOptions) { + super(connectString, new PostgreSQLConnectionFactory()); + } +} + +export class PostgreSQLClientPool extends SQLClientPool { + constructor(poolOptions: PoolConfig, connectString: PostgreSQLOptions) { + super(poolOptions, connectString, new PostgreSQLConnectionFactory()); + } +} diff --git a/src/lib/postgresql.ts b/src/lib/postgresql.ts new file mode 100644 index 00000000..bdcb8803 --- /dev/null +++ b/src/lib/postgresql.ts @@ -0,0 +1,295 @@ +import type { TableName } from '../types'; + +export function init(_dbName: string, _doNotCreateDatabase?: boolean): string[] { + return [ + 'CREATE TABLE sources (id SERIAL NOT NULL PRIMARY KEY, name TEXT);', + 'CREATE TABLE datapoints (id SERIAL NOT NULL PRIMARY KEY, name TEXT, type INTEGER);', + 'CREATE TABLE ts_number (id INTEGER NOT NULL, ts BIGINT, val REAL, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_string (id INTEGER NOT NULL, ts BIGINT, val TEXT, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_bool (id INTEGER NOT NULL, ts BIGINT, val BOOLEAN, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_counter (id INTEGER NOT NULL, ts BIGINT, val REAL);', + ]; +} + +export function destroy(_dbName: string): string[] { + return [ + 'DROP TABLE ts_counter;', + 'DROP TABLE ts_number;', + 'DROP TABLE ts_string;', + 'DROP TABLE ts_bool;', + 'DROP TABLE sources;', + 'DROP TABLE datapoints;', + ]; +} + +export function getFirstTs(_dbName: string, table: TableName): string { + return `SELECT id, MIN(ts) AS ts FROM ${table} GROUP BY id;`; +} + +export function insert( + _dbName: string, + index: number, + values: { + table: TableName; + state: { val: any; ts: number; ack?: boolean; q?: number }; + from?: number; + }[], +): string { + const insertValues: { [table: string]: string[] } = {}; + values.forEach(value => { + // state, from, table + insertValues[value.table] = insertValues[value.table] || []; + + if (!value.state || value.state.val === null || value.state.val === undefined) { + value.state.val = 'NULL'; + } else if (value.table === 'ts_string') { + value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; + } else if (value.table === 'ts_number') { + if (isNaN(value.state.val)) { + value.state.val = 'NULL'; + } + } + + if (value.table === 'ts_counter') { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val})`); + } else { + insertValues[value.table].push( + `(${index}, ${value.state.ts}, ${value.state.val}, ${!!value.state.ack}, ${value.from || 0}, ${value.state.q || 0})`, + ); + } + }); + + const query: string[] = []; + for (const table in insertValues) { + if (table === 'ts_counter') { + while (insertValues[table].length) { + query.push( + `INSERT INTO ts_counter (id, ts, val) VALUES ${insertValues[table].splice(0, 500).join(',')};`, + ); + } + } else { + while (insertValues[table].length) { + query.push( + `INSERT INTO ${table} (id, ts, val, ack, _from, q) VALUES ${insertValues[table].splice(0, 500).join(',')};`, + ); + } + } + } + + return query.join(' '); +} + +export function retention(_dbName: string, index: number, table: TableName, retention: number): string { + const d = new Date(); + d.setSeconds(-retention); + let query = `DELETE FROM ${table} WHERE`; + query += ` id=${index}`; + query += ` AND ts < ${d.getTime()}`; + query += ';'; + + return query; +} + +export function getIdSelect(_dbName: string, name?: string): string { + if (!name) { + return 'SELECT id, type, name FROM datapoints;'; + } + return `SELECT id, type, name FROM datapoints WHERE name='${name}';`; +} + +export function getIdInsert(_dbName: string, name: string, type: 0 | 1 | 2): string { + return `INSERT INTO datapoints (name, type) VALUES('${name}', ${type});`; +} + +export function getIdUpdate(_dbName: string, id: number, type: 0 | 1 | 2): string { + return `UPDATE datapoints SET type = ${type} WHERE id = ${id};`; +} + +export function getFromSelect(_dbName: string, name?: string): string { + if (name) { + return `SELECT id FROM sources WHERE name='${name}';`; + } + return 'SELECT id, name FROM sources;'; +} + +export function getFromInsert(dbName: string, values: string): string { + return `INSERT INTO sources (name) VALUES('${values}');`; +} + +export function getCounterDiff( + dbName: string, + options: { + index: number; + start: number; + end: number; + }, +): string { + // Take first real value after start + const subQueryStart = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC LIMIT 1`; + // Take last real value before the end + const subQueryEnd = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts DESC LIMIT 1`; + // Take last value before start + const subQueryFirst = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts< ${options.start} ORDER BY ts DESC LIMIT 1`; + // Take next value after end + const subQueryLast = `SELECT ts, val FROM \`${dbName}\`.ts_number WHERE id=${options.index} AND ts>= ${options.end} ORDER BY ts ASC LIMIT 1`; + // get values from counters where counter changed from up to down (e.g. counter changed) + const subQueryCounterChanges = `SELECT ts, val FROM \`${dbName}\`.ts_counter WHERE id=${options.index} AND ts>${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC`; + + return ( + `SELECT DISTINCT(a.ts), a.val from ((${subQueryFirst})\n` + + `UNION ALL \n(${subQueryStart})\n` + + `UNION ALL \n(${subQueryEnd})\n` + + `UNION ALL \n(${subQueryLast})\n` + + `UNION ALL \n(${subQueryCounterChanges})\n` + + `ORDER BY ts) a;` + ); +} + +export function getHistory( + _dbName: string, + table: string, + options: ioBroker.GetHistoryOptions & { index: number | null }, +): string { + let query = `SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${ + options.from ? ', sources.name as from' : '' + }${options.q ? ', q' : ''} FROM ${table}`; + + if (options.from) { + query += ` INNER JOIN sources ON sources.id=${table}._from`; + } + + let where = ''; + + if (options.index !== null) { + where += ` ${table}.id=${options.index}`; + } + if (options.end) { + where += `${where ? ' AND' : ''} ${table}.ts < ${options.end}`; + } + if (options.start) { + where += `${where ? ' AND' : ''} ${table}.ts >= ${options.start}`; + + //add last value before start + let subQuery; + let subWhere; + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${ + options.from ? ', sources.name as from' : '' + }${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + subQuery += ` INNER JOIN sources ON sources.id=${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${table}.id=${options.index}`; + } + if (options.ignoreNull) { + //subWhere += (subWhere ? " AND" : '') + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} ${table}.ts < ${options.start}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${table}.ts DESC LIMIT 1`; + where += ` UNION ALL (${subQuery})`; + + //add next value after end + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${ + options.from ? ', sources.name as from' : '' + }${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + subQuery += ` INNER JOIN sources ON sources.id=${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${table}.id=${options.index}`; + } + if (options.ignoreNull) { + //subWhere += (subWhere ? " AND" : '') + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} ${table}.ts >= ${options.end}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${table}.ts ASC LIMIT 1`; + where += ` UNION ALL(${subQuery})`; + } + + if (where) { + query += ` WHERE ${where}`; + } + + query += ' ORDER BY ts'; + + if ( + (!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries) + ) { + query += ' DESC'; + } else { + query += ' ASC'; + } + + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + query += ` LIMIT ${options.count + 2}`; + } + + query += ';'; + return query; +} + +export function deleteFromTable( + _dbName: string, + table: TableName, + index: number, + start?: number, + end?: number, +): string { + let query = `DELETE FROM ${table} WHERE`; + query += ` id=${index}`; + + if (start && end) { + query += ` AND ts>=${start} AND ts <= ${end}`; + } else if (start) { + query += ` AND ts=${start}`; + } + + query += ';'; + + return query; +} + +export function update( + _dbName: string, + index: number, + state: { val: number | string | boolean | null | undefined; ts: number; q?: number; ack?: boolean }, + from: number, + table: 'ts_bool' | 'ts_number' | 'ts_string' | 'ts_counter', +): string { + if (!state || state.val === null || state.val === undefined) { + state.val = 'NULL'; + } else if (table === 'ts_string') { + state.val = `'${state.val.toString().replace(/'/g, '')}'`; + } + + let query = `UPDATE ${table} SET `; + const vals = []; + if (state.val !== undefined) { + vals.push(`val=${state.val}`); + } + if (state.q !== undefined) { + vals.push(`q=${state.q}`); + } + if (from !== undefined) { + vals.push(`_from=${from}`); + } + if (state.ack !== undefined) { + vals.push(`ack=${!!state.ack}`); + } + query += vals.join(', '); + query += ' WHERE '; + query += ` id=${index}`; + query += ` AND ts=${state.ts}`; + query += ';'; + + return query; +} diff --git a/src/lib/sql-client-pool.ts b/src/lib/sql-client-pool.ts new file mode 100644 index 00000000..93b956bc --- /dev/null +++ b/src/lib/sql-client-pool.ts @@ -0,0 +1,417 @@ +import type { ConnectionFactory } from './connection-factory'; +import SQLClient from './sql-client'; + +export type PoolConfig = { + min_idle?: number; + max_idle?: number; + max_active?: number; + when_exhausted?: 'grow' | 'block' | 'fail'; + max_wait?: number; + wait_interval?: number; + max_retries?: number | null; + retry_interval?: number; + max_age?: number; + evictionRunInterval?: number; + eviction_run_length?: number; + unref_eviction_runner?: boolean; +}; + +export class SQLClientPool { + static MESSAGES = { + POOL_NOT_OPEN: "The pool is not open; please call 'open' before invoking this method.", + TOO_MANY_RETURNED: + 'More clients have been returned to the pool than were active. A client may have been returned twice.', + EXHAUSTED: 'The maximum number of clients are already active; cannot obtain a new client.', + MAX_WAIT: + 'The maximum number of clients are already active and the maximum wait time has been exceeded; cannot obtain a new client.', + INVALID: 'Unable to create a valid client.', + INTERNAL_ERROR: 'Internal error.', + INVALID_ARGUMENT: 'Invalid argument.', + NULL_RETURNED: 'A null object was returned.', + CLOSED_WITH_ACTIVE: 'The pool was closed, but some clients remain active (were never returned).', + }; + static DEFAULT_WAIT_INTERVAL = 50; + static DEFAULT_RETRY_INTERVAL = 50; + + private pool: SQLClient[] = []; + + private poolOptions: PoolConfig = {}; + + private poolIsOpen = false; + + private returned = 0; + + private active = 0; + + private evictionRunner: ReturnType | null = null; + + private readonly sqlOptions: any; + + private readonly factory: ConnectionFactory; + + constructor(poolOptions: PoolConfig, sqlOptions: any, factory: ConnectionFactory) { + this.factory = factory; + // CONFIGURATION OPTIONS: + // - `min_idle` - minimum number of idle connections in an "empty" pool + // - `max_idle` - maximum number of idle connections in a "full" pool + // - `max_active` - maximum number of connections active at one time + // - `when_exhausted` - what to do when max_active is reached (`grow`,`block`,`fail`), + // - `max_wait` - when `when_exhausted` is `block` max time (in millis) to wait before failure, use < 0 for no maximum + // - `wait_interval` - when `when_exhausted` is `BLOCK`, amount of time (in millis) to wait before rechecking if connections are available + // - `max_retries` - number of times to attempt to create another new connection when a newly created connection is invalid; when `null` no retry attempts will be made; when < 0 an infinite number of retries will be attempted + // - `retry_interval` - when `max_retries` is > 0, amount of time (in millis) to wait before retrying + // - `max_age` - when a positive integer, connections that have been idle for `max_age` milliseconds will be considered invalid and eligable for eviction + // - `evictionRunInterval` - when a positive integer, the number of milliseconds between eviction runs; during an eviction run idle connections will be tested for validity and if invalid, evicted from the pool + // - `eviction_run_length` - when a positive integer, the maxiumum number of connections to examine per eviction run (when not set, all idle connections will be evamined during each eviction run) + // - `unref_eviction_runner` - unless `false`, `unref` (https://nodejs.org/api/timers.html#timers_unref) will be called on the eviction run interval timer + this.sqlOptions = sqlOptions; + this.factory = factory; + this.open(poolOptions); + } + + create(callback: (err: Error | undefined, client: SQLClient) => void): void { + const client = new SQLClient(this.sqlOptions, this.factory); + client.connect(err => callback(err, client)); + } + + activate(client: SQLClient | undefined, callback: (err?: Error | null, client?: SQLClient) => void): void { + callback(null, client); + } + + validate( + client: SQLClient | undefined, + callback: (err: Error | null | undefined, valid: boolean, client?: SQLClient) => void, + ): void { + if ( + client && + (!client.pooled_at || + !this.poolOptions?.max_age || + Date.now() - client.pooled_at < this.poolOptions.max_age) + ) { + callback(null, true, client); + } else { + callback(null, false, client); + } + } + + passivate(client: SQLClient, callback: (err?: Error | null, client?: SQLClient) => void): void { + return callback(null, client); + } + + destroy(client: SQLClient | undefined, callback?: (err?: Error | null, client?: SQLClient) => void): void { + if (client) { + client.disconnect(callback); + } else { + callback?.(); + } + } + + open(opts: PoolConfig, callback?: (err?: Error | null) => void): void { + this._config(opts, err => { + this.poolIsOpen = true; + callback?.(err); + }); + } + + close(callback?: (err?: Error | null) => void): void { + this.poolIsOpen = false; + if (this.evictionRunner) { + this.evictionRunner.ref(); + clearTimeout(this.evictionRunner); + this.evictionRunner = null; + } + if (this.pool.length > 0) { + this.destroy(this.pool.shift(), () => this.close(callback)); + return; + } + if (this.active > 0) { + callback?.(new Error(SQLClientPool.MESSAGES.CLOSED_WITH_ACTIVE)); + return; + } + callback?.(); + } + + execute(sql: string, callback: (err: Error | null, result?: Array) => void): void { + this.borrow((err, client) => { + if (err) { + callback(err); + return; + } + if (client == null) { + callback(new Error('non-null client expected')); + return; + } + client.execute(sql, (err: Error | null, result?: Array): void => + this.return(client, () => callback(err, result)), + ); + }); + } + + borrow( + callback: (err?: Error | null, client?: SQLClient) => void, + blockedSince: number | null = null, + retryCount = 0, + ): void { + if (typeof callback !== 'function') { + throw new Error(SQLClientPool.MESSAGES.INVALID_ARGUMENT); + } + if (!this.poolIsOpen) { + return callback(new Error(SQLClientPool.MESSAGES.POOL_NOT_OPEN)); + } + if ( + this.poolOptions.max_active && + this.active >= this.poolOptions.max_active && + this.poolOptions.when_exhausted === 'fail' + ) { + return callback(new Error(SQLClientPool.MESSAGES.EXHAUSTED)); + } + if ( + this.poolOptions.max_active && + this.active >= this.poolOptions.max_active && + this.poolOptions.when_exhausted === 'block' + ) { + if (blockedSince && Date.now() - blockedSince >= this.poolOptions.max_wait!) { + return callback(new Error(SQLClientPool.MESSAGES.MAX_WAIT)); + } + blockedSince ||= Date.now(); + setTimeout(() => { + this.borrow(callback, blockedSince, retryCount); + }, this.poolOptions.wait_interval); + return; + } + if (this.pool.length > 0) { + const client = this.pool.shift()!; + this.#activateAndValidateOrDestroy(client, (err, valid, client) => { + if (err) { + callback(err); + return; + } + if (!valid || !client) { + this.borrow(callback); + return; + } + client.pooled_at = null; + client.borrowed_at = Date.now(); + this.active++; + return callback(null, client); + }); + return; + } + return this.create((err, client) => { + if (err) { + return callback(err); + } + return this.#activateAndValidateOrDestroy(client, (err, valid, client) => { + if (err) { + return callback(err); + } + if (!valid || !client) { + if (this.poolOptions.max_retries && this.poolOptions.max_retries > retryCount) { + return setTimeout(() => { + return this.borrow(callback, blockedSince, retryCount + 1); + }, this.poolOptions.retry_interval || 0); + } + return callback(new Error(SQLClientPool.MESSAGES.INVALID)); + } + client.pooled_at = null; + client.borrowed_at = Date.now(); + this.active++; + return callback(null, client); + }); + }); + } + + return(client: SQLClient, callback?: (err?: Error | null) => void): void { + if (!client) { + callback?.(new Error(SQLClientPool.MESSAGES.NULL_RETURNED)); + return; + } + if (this.active <= 0) { + callback?.(new Error(SQLClientPool.MESSAGES.TOO_MANY_RETURNED)); + return; + } + this.returned++; + this.active--; + this.passivate(client, (err, client) => { + if (err || !client) { + callback?.(err || new Error(SQLClientPool.MESSAGES.NULL_RETURNED)); + return; + } + client.pooled_at = Date.now(); + client.borrowed_at = null; + if (this.pool.length >= this.poolOptions.max_idle!) { + this.destroy(client, callback); + return; + } + this.pool.push(client); + callback?.(); + }); + return; + } + + _config(opts: PoolConfig, callback?: (err?: Error | null) => void): void { + if ( + opts.max_retries !== null && + opts.max_retries !== undefined && + (typeof opts.max_retries !== 'number' || opts.max_retries <= 0) + ) { + opts.max_retries = null; + } + if (opts.max_retries) { + if (opts.retry_interval && (typeof opts.retry_interval !== 'number' || opts.retry_interval <= 0)) { + opts.retry_interval = 0; + } else if (opts.retry_interval == null) { + opts.retry_interval = SQLClientPool.DEFAULT_RETRY_INTERVAL; + } + } + if (typeof opts.max_idle === 'number' && opts.max_idle < 0) { + opts.max_idle = Number.MAX_VALUE; + } else if (typeof opts.max_idle !== 'number') { + opts.max_idle = 0; + } + if (typeof opts.min_idle !== 'number' || opts.min_idle < 0) { + opts.min_idle = 0; + } + if (opts.min_idle > opts.max_idle) { + opts.min_idle = opts.max_idle; + } + if (typeof opts.max_active !== 'number' || opts.max_active < 0) { + opts.max_active = Number.MAX_VALUE; + } + if (typeof opts.max_wait !== 'number' || opts.max_wait < 0) { + opts.max_wait = Number.MAX_VALUE; + } + if (typeof opts.wait_interval !== 'number' || opts.wait_interval < 0) { + opts.wait_interval = SQLClientPool.DEFAULT_WAIT_INTERVAL; + } + if (opts.when_exhausted !== 'grow' && opts.when_exhausted !== 'block' && opts.when_exhausted !== 'fail') { + opts.when_exhausted = 'grow'; + } + if (opts.max_age === null || opts.max_age === undefined || opts.max_age < 0) { + opts.max_age = Number.MAX_VALUE; + } + this.poolOptions = opts; + this.#reconfig(callback); + } + + #reconfig(callback?: (err?: Error | null, borrowed?: SQLClient[]) => void): void { + if (this.evictionRunner) { + this.evictionRunner.ref(); + clearTimeout(this.evictionRunner); + this.evictionRunner = null; + } + this._evict(err => { + if (err) { + callback?.(err); + return; + } + this._prepopulate((err?: Error | null, borrowed?: SQLClient[]): void => { + if (this.poolOptions.evictionRunInterval && this.poolOptions.evictionRunInterval > 0) { + this.evictionRunner = setInterval(() => this.#evictionRun(), this.poolOptions.evictionRunInterval); + if (this.poolOptions.unref_eviction_runner !== false) { + this.evictionRunner.unref(); + } + } + callback?.(err, borrowed); + }); + }); + } + + _evict(callback: (err?: Error | null) => void): void { + return this.#evictionRun(0, callback); + } + + #evictionRun(numToCheck?: number | null, callback?: (err?: Error | null) => void): void { + const newPool = []; + let numChecked = 0; + while (this.pool.length > 0) { + const client = this.pool.shift()!; + if (numToCheck === null || numToCheck === undefined || numToCheck <= 0 || numChecked < numToCheck) { + numChecked += 1; + if (newPool.length < this.poolOptions.max_idle! && client) { + newPool.push(client); + } else { + client.disconnect(); + } + } else { + newPool.push(client); + } + } + this.pool = newPool; + callback?.(); + } + + _prepopulate(callback: (err?: Error | null, borrowed?: SQLClient[]) => void): void { + const n = this.poolOptions.min_idle! - this.pool.length; + if (n > 0) { + this.#borrowN(n, [], (err, borrowed) => { + if (err) { + return callback(err); + } + this.#returnN(borrowed, callback); + }); + return; + } + return callback(); + } + + #borrowN(n: number, borrowed: SQLClient[], callback: (err?: Error | null, borrowed?: SQLClient[]) => void): void { + if (n > borrowed.length) { + this.borrow((err, client) => { + if (client) { + borrowed.push(client); + } + if (err) { + this.#returnN(borrowed, () => callback(err)); + return; + } + this.#borrowN(n, borrowed, callback); + }); + return; + } + return callback(null, borrowed); + } + + #returnN(borrowed: SQLClient[] | undefined, callback: (err?: Error | null, borrowed?: SQLClient[]) => void): void { + if (!Array.isArray(borrowed)) { + callback(new Error(SQLClientPool.MESSAGES.INTERNAL_ERROR)); + return; + } + if (borrowed?.length > 0) { + const client = borrowed.shift()!; + this.return(client, () => this.#returnN(borrowed, callback)); + return; + } + return callback(null); + } + + #activateAndValidateOrDestroy( + client: SQLClient | undefined, + callback: (err: Error | null, valid: boolean, client: SQLClient | null) => void, + ): void { + return this.activate(client, (err, client) => { + if (err) { + if (client) { + this.destroy(client, () => callback(err, false, null)); + return; + } + callback(err, false, null); + return; + } + this.validate(client, (err, valid, client) => { + if (err) { + if (client) { + this.destroy(client, () => callback(err, false, null)); + return; + } + return callback(err, false, null); + } + if (!valid || !client) { + this.destroy(client, () => callback(null, false, null)); + return; + } + callback(null, true, client); + }); + }); + } +} diff --git a/src/lib/sql-client.ts b/src/lib/sql-client.ts new file mode 100644 index 00000000..7c7b2168 --- /dev/null +++ b/src/lib/sql-client.ts @@ -0,0 +1,116 @@ +import type { ConnectionFactory, SQLConnection } from './connection-factory'; +import { EventEmitter } from 'node:events'; + +export default class SQLClient extends EventEmitter { + private readonly options: any; + private factory: ConnectionFactory; + public pooled_at: number | null = null; + public borrowed_at: number | null = null; + public connected_at: number | null = null; + private connection: SQLConnection; + + constructor(options: any, connectionFactory: ConnectionFactory) { + super(); + this.options = options; + this.factory = connectionFactory; + } + + connect(callback?: (err?: Error) => void): void { + if (!this.connection) { + return this.factory.openConnection(this.options, (err, connection) => { + if (err) { + callback?.(err); + return; + } + this.connection = connection; + this.connected_at = Date.now(); + callback?.(); + }); + } + callback?.(); + } + + connectAsync(): Promise { + if (!this.connection) { + return new Promise((resolve, reject) => + this.factory.openConnection(this.options, (err, connection) => { + if (err) { + reject(err); + } else { + this.connection = connection; + this.connected_at = Date.now(); + resolve(); + } + }), + ); + } + return Promise.resolve(); + } + + disconnect(callback?: (err?: Error) => void): void { + if (this.connection) { + this.factory.closeConnection(this.connection, err => { + if (err) { + callback?.(err); + return; + } + this.connection = null; + this.connected_at = null; + callback?.(); + }); + return; + } + return callback?.(); + } + + disconnectAsync(): Promise { + if (this.connection) { + return new Promise((resolve, reject) => + this.factory.closeConnection(this.connection, err => { + if (err) { + reject(err); + } else { + this.connection = null; + this.connected_at = null; + resolve(); + } + }), + ); + } + return Promise.resolve(); + } + + execute(sql: string, callback: (err: Error | null, result?: Array) => void): void { + if (!this.connection) { + return this.connect(err => { + if (err) { + callback(err); + } else { + this.execute(sql, callback); + } + }); + } + this.factory.execute(this.connection, sql, (err?: Error | null, result?: Array): void => { + if (err) { + callback(err); + } else { + callback(null, result); + } + }); + } + + async executeAsync(sql: string): Promise | undefined> { + if (!this.connection) { + await this.connectAsync(); + } + return new Promise | undefined>((resolve, reject) => + this.factory.execute(this.connection, sql, (err?: Error | null, result?: Array): void => { + if (err) { + reject(err); + } else { + resolve(result); + } + }), + ); + } +} diff --git a/src/lib/sqlite.ts b/src/lib/sqlite.ts new file mode 100644 index 00000000..127aaa4e --- /dev/null +++ b/src/lib/sqlite.ts @@ -0,0 +1,296 @@ +import type { TableName } from '../types'; + +export function init(_dbName: string): string[] { + return [ + 'CREATE TABLE sources (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT);', + 'CREATE TABLE datapoints (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT, type INTEGER);', + 'CREATE TABLE ts_number (id INTEGER, ts INTEGER, val REAL, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_string (id INTEGER, ts INTEGER, val TEXT, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_bool (id INTEGER, ts INTEGER, val BOOLEAN, ack BOOLEAN, _from INTEGER, q INTEGER, PRIMARY KEY(id, ts));', + 'CREATE TABLE ts_counter (id INTEGER, ts INTEGER, val REAL, PRIMARY KEY(id, ts));', + ]; +} + +export function destroy(_dbName: string): string[] { + return [ + 'DROP TABLE ts_counter;', + 'DROP TABLE ts_number;', + 'DROP TABLE ts_string;', + 'DROP TABLE ts_bool;', + 'DROP TABLE sources;', + 'DROP TABLE datapoints;', + ]; +} + +export function getFirstTs(_dbName: string, table: TableName): string { + return `SELECT id, MIN(ts) AS ts FROM ${table} GROUP BY id;`; +} + +export function insert( + _dbName: string, + index: number, + values: { + table: TableName; + state: { val: any; ts: number; ack?: boolean; q?: number }; + from?: number; + }[], +): string { + const insertValues: { [table: string]: string[] } = {}; + values.forEach(value => { + // state, from, db + insertValues[value.table] ||= []; + + if (!value.state || value.state.val === null || value.state.val === undefined) { + value.state.val = 'NULL'; + } else if (value.table === 'ts_bool') { + value.state.val = value.state.val ? 1 : 0; + } else if (value.table === 'ts_string') { + value.state.val = `'${value.state.val.toString().replace(/'/g, '')}'`; + } else if (value.table === 'ts_number') { + if (isNaN(value.state.val)) { + value.state.val = 'NULL'; + } + } + + if (value.table === 'ts_counter') { + insertValues[value.table].push(`(${index}, ${value.state.ts}, ${value.state.val})`); + } else { + insertValues[value.table].push( + `(${index}, ${value.state.ts}, ${value.state.val}, ${value.state.ack ? 1 : 0}, ${value.from || 0}, ${value.state.q || 0})`, + ); + } + }); + + const query: string[] = []; + for (const table in insertValues) { + if (table === 'ts_counter') { + while (insertValues[table].length) { + query.push( + `INSERT INTO ts_counter (id, ts, val) VALUES ${insertValues[table].splice(0, 500).join(',')};`, + ); + } + } else { + while (insertValues[table].length) { + query.push( + `INSERT INTO ${table} (id, ts, val, ack, _from, q) VALUES ${insertValues[table].splice(0, 500).join(',')};`, + ); + } + } + } + + return query.join(' '); +} + +export function retention(_dbName: string, index: number, table: TableName, retention: number): string { + const d = new Date(); + d.setSeconds(-retention); + let query = `DELETE FROM ${table} WHERE`; + query += ` id=${index}`; + query += ` AND ts < ${d.getTime()}`; + query += ';'; + return query; +} + +export function getIdSelect(_dbName: string, name?: string): string { + if (!name) { + return 'SELECT id, type, name FROM datapoints;'; + } + return `SELECT id, type, name FROM datapoints WHERE name='${name}';`; +} + +export function getIdInsert(_dbName: string, name: string, type: 0 | 1 | 2): string { + return `INSERT INTO datapoints (name, type) VALUES('${name}', ${type});`; +} + +export function getIdUpdate(_dbName: string, id: number, type: 0 | 1 | 2): string { + return `UPDATE datapoints SET type = ${type} WHERE id = ${id};`; +} + +export function getFromSelect(_dbName: string, name?: string): string { + if (!name) { + return 'SELECT id, name FROM sources;'; + } + return `SELECT id FROM sources WHERE name='${name}';`; +} + +export function getFromInsert(_dbName: string, values: string): string { + return `INSERT INTO sources (name) VALUES('${values}');`; +} + +export function getCounterDiff( + _dbName: string, + options: { + index: number; + start: number; + end: number; + }, +): string { + // Take first real value after start + const subQueryStart = `SELECT ts, val FROM ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC LIMIT 1`; + // Take last real value before end + const subQueryEnd = `SELECT ts, val FROM ts_number WHERE id=${options.index} AND ts>=${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts DESC LIMIT 1`; + // Take last value before start + const subQueryFirst = `SELECT ts, val FROM ts_number WHERE id=${options.index} AND ts< ${options.start} ORDER BY ts DESC LIMIT 1`; + // Take next value after end + const subQueryLast = `SELECT ts, val FROM ts_number WHERE id=${options.index} AND ts>= ${options.end} ORDER BY ts ASC LIMIT 1`; + // get values from counters where counter changed from up to down (e.g. counter changed) + const subQueryCounterChanges = `SELECT ts, val FROM ts_counter WHERE id=${options.index} AND ts>${options.start} AND ts<${options.end} AND val IS NOT NULL ORDER BY ts ASC`; + + return ( + `SELECT DISTINCT(a.ts), a.val from ((${subQueryFirst})\n` + + `UNION ALL \n(${subQueryStart})\n` + + `UNION ALL \n(${subQueryEnd})\n` + + `UNION ALL \n(${subQueryLast})\n` + + `UNION ALL \n(${subQueryCounterChanges})\n` + + `ORDER BY ts) a;` + ); +} + +export function getHistory( + _dbName: string, + table: string, + options: ioBroker.GetHistoryOptions & { index: number | null }, +): string { + let query = `SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${ + options.from ? `, sources.name as 'from'` : '' + }${options.q ? ', q' : ''} FROM ${table}`; + + if (options.from) { + query += ` INNER JOIN sources ON sources.id=${table}._from`; + } + + let where = ''; + + if (options.index !== null) { + where += ` ${table}.id=${options.index}`; + } + if (options.end) { + where += `${where ? ' AND' : ''} ${table}.ts < ${options.end}`; + } + if (options.start) { + where += `${where ? ' AND' : ''} ${table}.ts >= ${options.start}`; + + // add last value before start + let subQuery; + let subWhere; + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${ + options.from ? `, sources.name as 'from'` : '' + }${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + subQuery += ` INNER JOIN sources ON sources.id=${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${table}.id=${options.index}`; + } + if (options.ignoreNull) { + // subWhere += (subWhere ? " AND" : '') + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} ${table}.ts < ${options.start}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${table}.ts DESC LIMIT 1`; + where += ` UNION ALL SELECT * from (${subQuery})`; + + // add next value after end + subQuery = ` SELECT ts, val${options.index !== null ? `, ${table}.id as id` : ''}${options.ack ? ', ack' : ''}${ + options.from ? `, sources.name as 'from'` : '' + }${options.q ? ', q' : ''} FROM ${table}`; + if (options.from) { + subQuery += ` INNER JOIN sources ON sources.id=${table}._from`; + } + subWhere = ''; + if (options.index !== null) { + subWhere += ` ${table}.id=${options.index}`; + } + if (options.ignoreNull) { + // subWhere += (subWhere ? " AND" : '') + " val <> NULL"; + } + subWhere += `${subWhere ? ' AND' : ''} ${table}.ts >= ${options.end}`; + if (subWhere) { + subQuery += ` WHERE ${subWhere}`; + } + subQuery += ` ORDER BY ${table}.ts ASC LIMIT 1`; + where += ` UNION ALL SELECT * from (${subQuery}) `; + } + + if (where) { + query += ` WHERE ${where}`; + } + + query += ' ORDER BY ts'; + + if ( + (!options.start && options.count) || + (options.aggregate === 'none' && options.count && options.returnNewestEntries) + ) { + query += ' DESC'; + } else { + query += ' ASC'; + } + + if ((!options.start && options.count) || (options.aggregate === 'none' && options.count)) { + query += ` LIMIT ${options.count + 2}`; + } + + query += ';'; + return query; +} + +export function deleteFromTable( + _dbName: string, + table: TableName, + index: number, + start?: number, + end?: number, +): string { + let query = `DELETE FROM ${table} WHERE`; + query += ` id=${index}`; + + if (start && end) { + query += ` AND ts>=${start} AND ts <= ${end}`; + } else if (start) { + query += ` AND ts=${start}`; + } + + query += ';'; + + return query; +} + +export function update( + _dbName: string, + index: number, + state: { val: number | string | boolean | null | undefined; ts: number; q?: number; ack?: boolean }, + from: number, + table: 'ts_bool' | 'ts_number' | 'ts_string' | 'ts_counter', +): string { + if (!state || state.val === null || state.val === undefined) { + state.val = 'NULL'; + } else if (table === 'ts_string') { + state.val = `'${state.val.toString().replace(/'/g, '')}'`; + } + + let query = `UPDATE ${table} SET `; + const vals = []; + if (state.val !== undefined) { + vals.push(`val=${state.val}`); + } + if (state.q !== undefined) { + vals.push(`q=${state.q}`); + } + if (from !== undefined) { + vals.push(`_from=${from}`); + } + if (state.ack !== undefined) { + vals.push(`ack=${state.ack ? 1 : 0}`); + } + query += vals.join(', '); + query += ' WHERE '; + query += ` id=${index}`; + query += ` AND ts=${state.ts}`; + query += ';'; + + return query; +} diff --git a/src/lib/sqlite3-client.ts b/src/lib/sqlite3-client.ts new file mode 100644 index 00000000..60ff7e0c --- /dev/null +++ b/src/lib/sqlite3-client.ts @@ -0,0 +1,65 @@ +import { ConnectionFactory } from './connection-factory'; +import SQLClient from './sql-client'; +import { SQLClientPool, type PoolConfig } from './sql-client-pool'; + +import type { Database } from 'sqlite3'; + +type SQLite3Options = { fileName: string; mode?: number }; + +export type { SQLite3Options }; + +export class SQLite3ConnectionFactory extends ConnectionFactory { + private Database: typeof Database | undefined; + + openConnection(options: SQLite3Options, callback: (err: Error | null, connection?: Database) => void): void { + if (!this.Database) { + void import('sqlite3').then(sqlite3 => { + this.Database = sqlite3.default.Database; + this.openConnection(options, callback); + }); + return; + } + + if (options.mode) { + const db = new this.Database(options.fileName, options.mode, (err: Error | null): void => { + if (err) { + callback(err); + } else { + callback(null, db); + } + }); + return; + } + const db = new this.Database(options.fileName, (err: Error | null): void => { + if (err) { + callback(err); + } else { + callback(null, db); + } + }); + } + + closeConnection(db: Database, callback?: (err?: Error | null) => void): void { + if (db) { + db.close(callback); + } else { + callback?.(); + } + } + + execute(db: Database, sql: string, callback: (err: Error | null, result?: Array) => void): void { + db.all(sql, [], callback); + } +} + +export class SQLite3Client extends SQLClient { + constructor(sqliteOptions: SQLite3Options) { + super(sqliteOptions, new SQLite3ConnectionFactory()); + } +} + +export class SQLite3ClientPool extends SQLClientPool { + constructor(poolOptions: PoolConfig, sqliteOptions: SQLite3Options) { + super(poolOptions, sqliteOptions, new SQLite3ConnectionFactory()); + } +} diff --git a/src/lib/types.d.ts b/src/lib/types.d.ts new file mode 100644 index 00000000..a11b7146 --- /dev/null +++ b/src/lib/types.d.ts @@ -0,0 +1,154 @@ +export type SmartDate = { + // From 1 to 31 + date: number; + // From 1 to 12 + month: number; + // from 1970 to 2300 + year: number; + /** If "end" is true, the time will be calculated as "start of this day/month/hour" - 1ms */ + end?: boolean; + /** Time zone (default is 1 - germany) */ + timeZone?: number; +}; + +export interface DataEntry { + ts: number; + + [valueName: string]: number; +} + +export interface IobDataEntry { + val: number | null; + ts: number; + time?: string; + id?: string; + ack?: boolean; + from?: string; + q?: number; + /** Interpolated value */ + i?: boolean; +} + +interface GetHistoryOptions { + instance?: string; + + /** Start time in ms */ + start?: number; + /** End time in ms. If not defined, it is "now" */ + end?: number; + /** Step in ms of intervals. Used in aggregate (max, min, average, total, ...) */ + step?: number; + + /** Start of smart intervals. It calculates the statistics according to real month length */ + smartStart?: SmartDate; + /** Type of smart intervals */ + smartType?: 'hour' | 'day' | 'month'; + /** End of smart intervals. It calculates the statistics according to real month length. If not defined, the end is "now" */ + smartEnd?: SmartDate; + + /** number of values if aggregate is 'onchange' or number of intervals if other aggregate method. Count will be ignored if step is set, else default is 500 if not set */ + count?: number; + /** if `from` field should be included in answer */ + from?: boolean; + /** if `ack` field should be included in answer */ + ack?: boolean; + /** if `q` field should be included in answer */ + q?: boolean; + /** if `id` field should be included in answer */ + addId?: boolean; + /** do not return more entries than limit */ + limit?: number; + /** round result to number of digits after decimal point */ + round?: number; + /** if null values should be included (false), replaced by last not null value (true) or replaced with 0 (0) */ + ignoreNull?: boolean | 0; + /** This number will be returned in answer, so the client can assign the request for it */ + sessionId?: number; + /** aggregate method (Default: 'average') */ + aggregate?: + | 'onchange' + | 'minmax' + | 'min' + | 'max' + | 'average' + | 'total' + | 'count' + | 'none' + | 'percentile' + | 'quantile' + | 'integral' + | 'integralTotal'; + /** Returned data is normally sorted ascending by date, this option lets you return the newest instead of the oldest values if the number of returned points is limited */ + returnNewestEntries?: boolean; + /** By default, the additional border values are returned to optimize charting. Set this option to true if this is not wanted (e.g. for script data processing) */ + removeBorderValues?: boolean; + /** when using aggregate method `percentile` defines the percentile level (0..100)(defaults to 50) */ + percentile?: number; + /** when using aggregate method `quantile` defines the quantile level (0..1)(defaults to 0.5) */ + quantile?: number; + /** when using aggregate method `integral` defines the unit in seconds (defaults to 60s). e.g. to get integral in hours for Wh or such, set to 3600. */ + integralUnit?: number; + /** when using aggregate method `integral` defines the interpolation method (defaults to `none`). */ + integralInterpolation?: 'none' | 'linear'; + /** This means, that the data was pre-aggregated when stored (e.g. by influxdb) */ + preAggregated?: boolean; +} + +export interface TimeInterval { + start: number; + end: number; + startS?: string; + endS?: string; +} + +export interface GetStatistics { + timeType: 'hour' | 'day' | 'month'; + startDate: SmartDate; + endDate?: SmartDate; + // Object ID + id?: string; + // Winter TimeZone in hours. For Germany, it is +1; + timeZone?: number; +} + +export interface GetHistoryOptionsExtended extends GetHistoryOptions { + uuid?: string; + id?: string; + path?: string; + + time?: boolean; + humanTime?: boolean; + + endS?: string; + startS?: string; + + pretty?: boolean; + preAggregated?: boolean; + + logDebug?: boolean; +} + +export interface InternalHistoryOptions extends GetHistoryOptions { + id?: string; + logDebug?: boolean; + log?: typeof console.log; + processing?: { + val: { ts: number | null; val: number | null }; + max: { ts: number | null; val: number | null }; + min: { ts: number | null; val: number | null }; + start: { ts: number | null; val: number | null }; + end: { ts: number | null; val: number | null }; + }[]; + + result?: IobDataEntry[]; + + overallLength?: number; + maxIndex?: number; + averageCount?: number[]; + quantileDataPoints?: number[][]; + integralDataPoints?: IobDataEntry[][]; + totalIntegralDataPoints?: IobDataEntry[]; + + timeIntervals?: TimeInterval[]; + currentTimeInterval?: number; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 00000000..4006df32 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,4401 @@ +import { Adapter, type AdapterOptions, getAbsoluteDefaultDataDir } from '@iobroker/adapter-core'; // Get common adapter utils +import { sendResponseCounter, sendResponse } from './lib/aggregate'; +import { existsSync, mkdirSync } from 'node:fs'; +import { join, normalize } from 'node:path'; +import { DockerManagerOfOwnContainers, type ContainerConfig } from '@iobroker/plugin-docker'; + +import type { + DbType, + SqlAdapterConfig, + SqlAdapterConfigTyped, + SqlCustomConfig, + SqlCustomConfigTyped, + TableName, +} from './types'; + +import * as MSSQL from './lib/mssql'; +import * as MySQL from './lib/mysql'; +import * as PostgreSQL from './lib/postgresql'; +import * as SQLite from './lib/sqlite'; + +import { MSSQLClientPool, MSSQLClient, type MSSQLOptions } from './lib/mssql-client'; +import { MySQL2ClientPool, MySQL2Client, type MySQLOptions } from './lib/mysql-client'; +import { PostgreSQLClientPool, PostgreSQLClient, type PostgreSQLOptions } from './lib/postgresql-client'; +import { SQLite3ClientPool, SQLite3Client, type SQLite3Options } from './lib/sqlite3-client'; +import type { SQLClientPool, PoolConfig } from './lib/sql-client-pool'; +import type SQLClient from './lib/sql-client'; +import type { IobDataEntry } from './lib/types'; + +export interface IobDataEntryEx extends Omit { + val: string | boolean | number | null; + lc: number; +} + +type SQLFunc = { + init: (dbName: string, doNotCreateDatabase?: boolean) => string[]; + destroy: (dbName: string) => string[]; + getFirstTs: (dbName: string, table: 'ts_string' | 'ts_number' | 'ts_bool' | 'ts_counter') => string; + insert: ( + dbName: string, + index: number, + values: { + table: 'ts_string' | 'ts_number' | 'ts_bool' | 'ts_counter'; + state: { val: any; ts: number; ack?: boolean; q?: number }; + from?: number; + }[], + ) => string; + retention: ( + dbName: string, + index: number, + table: 'ts_string' | 'ts_number' | 'ts_bool' | 'ts_counter', + retention: number, + ) => string; + getIdSelect: (dbName: string, name?: string) => string; + getIdInsert: (dbName: string, name: string, type: 0 | 1 | 2) => string; + getIdUpdate: (dbName: string, index: number, type: 0 | 1 | 2) => string; + getFromSelect: (dbName: string, name?: string) => string; + getFromInsert: (dbName: string, values: string) => string; + getCounterDiff: ( + dbName: string, + options: { + index: number; + start: number; + end: number; + }, + ) => string; + getHistory: ( + dbName: string, + table: string, + options: ioBroker.GetHistoryOptions & { index: number | null }, + ) => string; + deleteFromTable: ( + dbName: string, + table: 'ts_string' | 'ts_number' | 'ts_bool' | 'ts_counter', + index: number, + start?: number, + end?: number, + ) => string; + update: ( + dbName: string, + index: number, + state: { val: number | string | boolean | null | undefined; ts: number; q?: number; ack?: boolean }, + from: number, + table: 'ts_bool' | 'ts_number' | 'ts_string' | 'ts_counter', + ) => string; +}; + +const SQLFuncs: Record = { + mssql: { + init: MSSQL.init, + destroy: MSSQL.destroy, + getFirstTs: MSSQL.getFirstTs, + insert: MSSQL.insert, + retention: MSSQL.retention, + getIdSelect: MSSQL.getIdSelect, + getIdInsert: MSSQL.getIdInsert, + getIdUpdate: MSSQL.getIdUpdate, + getFromSelect: MSSQL.getFromSelect, + getFromInsert: MSSQL.getFromInsert, + getCounterDiff: MSSQL.getCounterDiff, + getHistory: MSSQL.getHistory, + deleteFromTable: MSSQL.deleteFromTable, + update: MSSQL.update, + }, + mysql: { + init: MySQL.init, + destroy: MySQL.destroy, + getFirstTs: MySQL.getFirstTs, + insert: MySQL.insert, + retention: MySQL.retention, + getIdSelect: MySQL.getIdSelect, + getIdInsert: MySQL.getIdInsert, + getIdUpdate: MySQL.getIdUpdate, + getFromSelect: MySQL.getFromSelect, + getFromInsert: MySQL.getFromInsert, + getCounterDiff: MySQL.getCounterDiff, + getHistory: MySQL.getHistory, + deleteFromTable: MySQL.deleteFromTable, + update: MySQL.update, + }, + postgresql: { + init: PostgreSQL.init, + destroy: PostgreSQL.destroy, + getFirstTs: PostgreSQL.getFirstTs, + insert: PostgreSQL.insert, + retention: PostgreSQL.retention, + getIdSelect: PostgreSQL.getIdSelect, + getIdInsert: PostgreSQL.getIdInsert, + getIdUpdate: PostgreSQL.getIdUpdate, + getFromSelect: PostgreSQL.getFromSelect, + getFromInsert: PostgreSQL.getFromInsert, + getCounterDiff: PostgreSQL.getCounterDiff, + getHistory: PostgreSQL.getHistory, + deleteFromTable: PostgreSQL.deleteFromTable, + update: PostgreSQL.update, + }, + sqlite: { + init: SQLite.init, + destroy: SQLite.destroy, + getFirstTs: SQLite.getFirstTs, + insert: SQLite.insert, + retention: SQLite.retention, + getIdSelect: SQLite.getIdSelect, + getIdInsert: SQLite.getIdInsert, + getIdUpdate: SQLite.getIdUpdate, + getFromSelect: SQLite.getFromSelect, + getFromInsert: SQLite.getFromInsert, + getCounterDiff: SQLite.getCounterDiff, + getHistory: SQLite.getHistory, + deleteFromTable: SQLite.deleteFromTable, + update: SQLite.update, + }, +}; + +const clients = { + postgresql: { multiRequests: true }, + mysql: { multiRequests: true }, + sqlite: { multiRequests: false }, + mssql: { multiRequests: true }, +}; + +const types: { [valueType: string]: 0 | 1 | 2 } = { + number: 0, + string: 1, + boolean: 2, + object: 1, +}; +const dbNames: TableName[] = ['ts_number', 'ts_string', 'ts_bool']; + +const storageTypes: ('Number' | 'String' | 'Boolean')[] = ['Number', 'String', 'Boolean']; + +function isEqual(a: any, b: any): boolean { + //console.log('Compare ' + JSON.stringify(a) + ' with ' + JSON.stringify(b)); + // Create arrays of property names + if (a === null || a === undefined || b === null || b === undefined) { + return a === b; + } + + const aProps = Object.getOwnPropertyNames(a); + const bProps = Object.getOwnPropertyNames(b); + + // If the number of properties is different, + // objects are not equivalent + if (aProps.length !== bProps.length) { + //console.log('num props different: ' + JSON.stringify(aProps) + ' / ' + JSON.stringify(bProps)); + return false; + } + + for (let i = 0; i < aProps.length; i++) { + const propName = aProps[i]; + + if (typeof a[propName] !== typeof b[propName]) { + //console.log('type props ' + propName + ' different'); + return false; + } else if (typeof a[propName] === 'object') { + if (!isEqual(a[propName], b[propName])) { + return false; + } + } else { + // If values of same property are not equal, + // objects are not equivalent + if (a[propName] !== b[propName]) { + //console.log('props ' + propName + ' different'); + return false; + } + } + } + + // If we made it this far, objects + // are considered equivalent + return true; +} + +const MAX_TASKS = 100; + +type SQLPointConfig = { + realId: string; + relogTimeout: NodeJS.Timeout | null; + timeout: NodeJS.Timeout | null; + type: 0 | 1 | 2; + index: number; + dbType: 0 | 1 | 2; + config: SqlCustomConfigTyped; + state: IobDataEntryEx | null; + skipped: IobDataEntryEx | undefined | null; + ts: number | null; + lastCheck: number; + lastLogTime: number; + list: { state: IobDataEntryEx; from: number; table: TableName }[]; + inFlight: { [inFlightId: string]: { state: IobDataEntryEx; from: number; table: TableName }[] }; + isRunning?: { id: string; state: IobDataEntryEx; isCounter: boolean; cb?: (err?: Error | null) => void }[]; +}; + +function sortByTs( + a: IobDataEntryEx & { + date?: Date; + id?: string | undefined; + }, + b: IobDataEntryEx & { + date?: Date; + id?: string | undefined; + }, +): 0 | 1 | -1 { + const aTs = a.ts; + const bTs = b.ts; + return aTs < bTs ? -1 : aTs > bTs ? 1 : 0; +} + +type TaskUserQuery = { operation: 'userQuery'; msg: ioBroker.Message }; +type TaskDelete = { operation: 'delete'; query: string }; +type TaskInsert = { + operation: 'insert'; + index: number; + list: { state: IobDataEntryEx; from: number; table: TableName }[]; + id: string; + callback?: (err?: Error | null) => void; +}; +type TaskQuery = { + operation: 'query'; + query: string; + id: string; + callback?: (err?: Error | null, results?: IobDataEntry[]) => void; +}; +type TaskSelect = { + operation: 'select'; + query: string; + options: ioBroker.GetHistoryOptions & { id: string | null; index: number | null }; + callback?: (err: Error | null, result?: (IobDataEntryEx & { date?: Date; id?: string })[]) => void; +}; + +export class SqlAdapter extends Adapter { + declare config: SqlAdapterConfigTyped; + private readonly sqlDPs: { [aliasId: string]: SQLPointConfig } = {}; + private readonly from: { [from: string]: number } = {}; + private readonly tasks: (TaskUserQuery | TaskDelete | TaskInsert | TaskQuery | TaskSelect)[] = []; + private readonly tasksReadType: { + id: string; + state: IobDataEntryEx; + cb?: ((err?: Error | null) => void) | null; + }[] = []; + private readonly tasksStart: { id: string; now: number }[] = []; + private readonly isFromRunning: { + [id: string]: { id: string; state: IobDataEntryEx; cb?: (err?: Error | null) => void }[] | null; + } = {}; + private readonly aliasMap: { [alias: string]: string } = {}; + + private finished: (() => void)[] | boolean = false; + private sqlConnected: boolean | null = null; + private multiRequests = true; + private subscribeAll = false; + private clientPool: SQLClientPool | null = null; + private reconnectTimeout: NodeJS.Timeout | null = null; + private testConnectTimeout: NodeJS.Timeout | null = null; + private dpOverviewTimeout: NodeJS.Timeout | null = null; + private bufferChecker: NodeJS.Timeout | null = null; + private activeConnections = 0; + private poolBorrowGuard: ((err: Error | null | undefined, client: SQLClient) => void)[] = []; // required until https://github.com/intellinote/sql-client/issues/7 is fixed + private readonly logConnectionUsage = true; + private postgresDbCreated = false; + private lockTasks = false; + private sqlFuncs: SQLFunc | null = null; + + public constructor(options: Partial = {}) { + super({ + ...options, + name: 'sql', + ready: () => this.main(), + message: (obj: ioBroker.Message) => this.processMessage(obj), + stateChange: (id: string, state: ioBroker.State | null | undefined): void => { + id = this.aliasMap[id] || id; + this.pushHistory(id, state as IobDataEntryEx); + }, + objectChange: (id: string, obj: ioBroker.Object | null | undefined): void => { + let tmpState: IobDataEntryEx | undefined; + const now = Date.now(); + const formerAliasId = this.aliasMap[id] || id; + + if ( + obj?.common?.custom?.[this.namespace] && + typeof obj.common.custom[this.namespace] === 'object' && + obj.common.custom[this.namespace].enabled + ) { + const realId = id; + let checkForRemove = true; + + if (obj.common.custom?.[this.namespace]?.aliasId) { + if (obj.common.custom[this.namespace].aliasId !== id) { + this.aliasMap[id] = obj.common.custom[this.namespace].aliasId; + this.log.debug(`Registered Alias: ${id} --> ${this.aliasMap[id]}`); + id = this.aliasMap[id]; + checkForRemove = false; + } else { + this.log.warn(`Ignoring Alias-ID because identical to ID for ${id}`); + obj.common.custom[this.namespace].aliasId = ''; + } + } + + if (checkForRemove && this.aliasMap[id]) { + this.log.debug(`Removed Alias: ${id} !-> ${this.aliasMap[id]}`); + delete this.aliasMap[id]; + } + + if (!this.sqlDPs[formerAliasId]?.config && !this.subscribeAll) { + // un-subscribe + for (const _id in this.sqlDPs) { + if ( + Object.prototype.hasOwnProperty.call(this.sqlDPs, _id) && + Object.prototype.hasOwnProperty.call(this.sqlDPs, this.sqlDPs[_id].realId) + ) { + this.unsubscribeForeignStates(this.sqlDPs[_id].realId); + } + } + this.subscribeAll = true; + this.subscribeForeignStates('*'); + } + + if (this.sqlDPs[id] && this.sqlDPs[id].index === undefined) { + this.getId(id, this.sqlDPs[id].dbType, () => + this.reInit(id, realId, formerAliasId, obj as ioBroker.StateObject), + ); + } else { + this.reInit(id, realId, formerAliasId, obj as ioBroker.StateObject); + } + } else { + if (this.aliasMap[id]) { + this.log.debug(`Removed Alias: ${id} !-> ${this.aliasMap[id]}`); + delete this.aliasMap[id]; + } + const sqlDP = this.sqlDPs[id]; + + id = formerAliasId; + + if (sqlDP?.config) { + this.log.info(`disabled logging of ${id}`); + if (sqlDP.relogTimeout) { + clearTimeout(sqlDP.relogTimeout); + sqlDP.relogTimeout = null; + } + if (sqlDP.timeout) { + clearTimeout(sqlDP.timeout); + sqlDP.timeout = null; + } + + if (sqlDP.state) { + tmpState = { ...sqlDP.state }; + } + const state = sqlDP.state ? tmpState || null : null; + + if (sqlDP.config) { + sqlDP.config.enabled = false; + if (sqlDP.skipped && !sqlDP.config.disableSkippedValueLogging) { + this.pushValueIntoDB(id, sqlDP.skipped, false, true); + sqlDP.skipped = null; + } + + if (this.config.writeNulls) { + const nullValue: IobDataEntryEx = { + val: null, + ts: now, + lc: now, + q: 0x40, + from: `system.adapter.${this.namespace}`, + ack: true, + }; + + if (sqlDP.config.changesOnly && state && state.val !== null) { + ((_id, _state, _nullValue) => { + _state.ts = now; + _state.from = `system.adapter.${this.namespace}`; + nullValue.ts += 4; + nullValue.lc += 4; // because of MS SQL + this.log.debug(`Write 1/2 "${_state.val}" _id: ${_id}`); + this.pushValueIntoDB(_id, _state, false, true, () => { + // terminate values with null to indicate adapter stop. timestamp + 1 + this.log.debug(`Write 2/2 "null" _id: ${_id}`); + this.pushValueIntoDB( + _id, + _nullValue, + false, + false, + () => delete this.sqlDPs[id], + ); + }); + })(id, state, nullValue); + } else { + // terminate values with null to indicate adapter stop. timestamp + 1 + this.log.debug(`Write 0 NULL _id: ${id}`); + this.pushValueIntoDB(id, nullValue, false, false, () => delete this.sqlDPs[id]); + } + } else { + this.storeCached(id, () => delete this.sqlDPs[id]); + } + } else { + delete this.sqlDPs[id]; + } + } + } + }, + unload: (callback: () => void): void => { + void this.finish(callback); + }, + }); + } + + borrowClientFromPool(callback: (err: Error | null | undefined, client?: SQLClient) => void): void { + if (!this.clientPool) { + this.setConnected(false); + return callback(new Error('No database connection')); + } + this.setConnected(true); + + if (this.activeConnections >= this.config.maxConnections) { + if (this.logConnectionUsage) { + this.log.debug(`Borrow connection not possible: ${this.activeConnections} >= Max - Store for Later`); + } + this.poolBorrowGuard.push(callback); + return; + } + this.activeConnections++; + if (this.logConnectionUsage) { + this.log.debug(`Borrow connection from pool: ${this.activeConnections} now`); + } + this.clientPool.borrow((err: Error | null | undefined, client?: SQLClient): void => { + if (!err && client) { + // make sure we always have at least one error listener to prevent crashes + if (client.on && client.listenerCount && !client.listenerCount('error')) { + client.on('error', (err: string): void => this.log.warn(`SQL client error: ${err}`)); + } + } else if (!client) { + this.activeConnections--; + } + + callback(err, client); + }); + } + + returnClientToPool(client?: SQLClient): void { + if (client) { + this.activeConnections--; + } + if (this.logConnectionUsage) { + this.log.debug(`Return connection to pool: ${this.activeConnections} now`); + } + if (this.clientPool && client) { + if (this.poolBorrowGuard.length) { + this.activeConnections++; + if (this.logConnectionUsage) { + this.log.debug(`Borrow returned connection directly: ${this.activeConnections} now`); + } + const callback = this.poolBorrowGuard.shift()!; + callback(null, client); + } else { + try { + this.clientPool.return(client); + } catch { + // Ignore + } + } + } + } + + normalizeCustomConfig(customConfig: SqlCustomConfig): SqlCustomConfigTyped { + // maxLength + if (!customConfig.maxLength && customConfig.maxLength !== '0' && customConfig.maxLength !== 0) { + customConfig.maxLength = this.config.maxLength || 0; + } else { + customConfig.maxLength = parseInt(customConfig.maxLength as string, 10); + } + + // retention + if (customConfig.retention || customConfig.retention === 0) { + customConfig.retention = parseInt(customConfig.retention as string, 10) || 0; + } else { + customConfig.retention = this.config.retention; + } + if (customConfig.retention === -1) { + // customRetentionDuration + if ( + customConfig.customRetentionDuration !== undefined && + customConfig.customRetentionDuration !== null && + customConfig.customRetentionDuration !== '' + ) { + customConfig.customRetentionDuration = + parseInt(customConfig.customRetentionDuration as string, 10) || 0; + } else { + customConfig.customRetentionDuration = this.config.customRetentionDuration; + } + customConfig.retention = customConfig.customRetentionDuration * 24 * 60 * 60; + } + + // debounceTime and debounce compatibility handling + if (!customConfig.blockTime && customConfig.blockTime !== '0' && customConfig.blockTime !== 0) { + if (!customConfig.debounce && customConfig.debounce !== '0' && customConfig.debounce !== 0) { + customConfig.blockTime = this.config.blockTime || 0; + } else { + customConfig.blockTime = parseInt(customConfig.debounce as string, 10) || 0; + } + } else { + customConfig.blockTime = parseInt(customConfig.blockTime as string, 10) || 0; + } + if (!customConfig.debounceTime && customConfig.debounceTime !== '0' && customConfig.debounceTime !== 0) { + customConfig.debounceTime = this.config.debounceTime || 0; + } else { + customConfig.debounceTime = parseInt(customConfig.debounceTime as string, 10) || 0; + } + + // changesOnly + customConfig.changesOnly = customConfig.changesOnly === true || customConfig.changesOnly === 'true'; + + // ignoreZero + customConfig.ignoreZero = customConfig.ignoreZero === true || customConfig.ignoreZero === 'true'; + + // round + if (customConfig.round !== null && customConfig.round !== undefined && customConfig.round !== '') { + customConfig.round = parseInt(customConfig.round as string, 10); + if (!isFinite(customConfig.round) || customConfig.round < 0) { + customConfig.round = this.config.round as number; + } else { + customConfig.round = Math.pow(10, parseInt(customConfig.round as unknown as string, 10)); + } + } else { + customConfig.round = this.config.round as number; + } + + // ignoreAboveNumber + if ( + customConfig.ignoreAboveNumber !== undefined && + customConfig.ignoreAboveNumber !== null && + customConfig.ignoreAboveNumber !== '' + ) { + customConfig.ignoreAboveNumber = parseFloat(customConfig.ignoreAboveNumber as string) || null; + } + + // ignoreBelowNumber incl. ignoreBelowZero compatibility handling + if ( + customConfig.ignoreBelowNumber !== undefined && + customConfig.ignoreBelowNumber !== null && + customConfig.ignoreBelowNumber !== '' + ) { + customConfig.ignoreBelowNumber = parseFloat(customConfig.ignoreBelowNumber as string) || null; + } else if (customConfig.ignoreBelowZero === 'true' || customConfig.ignoreBelowZero === true) { + customConfig.ignoreBelowNumber = 0; + } + + // disableSkippedValueLogging + if ( + customConfig.disableSkippedValueLogging !== undefined && + customConfig.disableSkippedValueLogging !== null && + (customConfig.disableSkippedValueLogging as any) !== '' + ) { + customConfig.disableSkippedValueLogging = + customConfig.disableSkippedValueLogging === 'true' || customConfig.disableSkippedValueLogging === true; + } else { + customConfig.disableSkippedValueLogging = this.config.disableSkippedValueLogging; + } + + // enableDebugLogs + if ( + customConfig.enableDebugLogs !== undefined && + customConfig.enableDebugLogs !== null && + (customConfig.enableDebugLogs as any) !== '' + ) { + customConfig.enableDebugLogs = + customConfig.enableDebugLogs === 'true' || customConfig.enableDebugLogs === true; + } else { + customConfig.enableDebugLogs = this.config.enableDebugLogs; + } + + // changesRelogInterval + if (customConfig.changesRelogInterval || customConfig.changesRelogInterval === 0) { + customConfig.changesRelogInterval = parseInt(customConfig.changesRelogInterval as string, 10) || 0; + } else { + customConfig.changesRelogInterval = this.config.changesRelogInterval; + } + + // changesMinDelta + if (customConfig.changesMinDelta || customConfig.changesMinDelta === 0) { + customConfig.changesMinDelta = parseFloat(customConfig.changesMinDelta.toString().replace(/,/g, '.')) || 0; + } else { + customConfig.changesMinDelta = this.config.changesMinDelta; + } + + // storageType + if (!customConfig.storageType) { + customConfig.storageType = false; + } + + // add one day if retention is too small + if (customConfig.retention && customConfig.retention <= 604800) { + customConfig.retention += 86400; + } + return customConfig as SqlCustomConfigTyped; + } + + reInit(id: string, realId: string, formerAliasId: string, obj: ioBroker.StateObject): void { + const customConfig = this.normalizeCustomConfig(obj.common.custom?.[this.namespace]); + if (this.sqlDPs[formerAliasId]?.config && isEqual(customConfig, this.sqlDPs[formerAliasId].config)) { + if (obj.common.custom?.[this.namespace].enableDebugLogs) { + this.log.debug(`Object ${id} unchanged. Ignore`); + } + return; + } + + // relogTimeout + if (this.sqlDPs[formerAliasId] && this.sqlDPs[formerAliasId].relogTimeout) { + clearTimeout(this.sqlDPs[formerAliasId].relogTimeout); + this.sqlDPs[formerAliasId].relogTimeout = null; + } + + const writeNull = !this.sqlDPs[id]?.config; + + this.sqlDPs[id] = { + config: customConfig, + state: this.sqlDPs[id]?.state || null, + list: this.sqlDPs[id]?.list || [], + inFlight: this.sqlDPs[id]?.inFlight || {}, + timeout: this.sqlDPs[id]?.timeout || null, + ts: this.sqlDPs[id]?.ts || null, + lastLogTime: 0, + relogTimeout: null, + realId: realId, + lastCheck: this.sqlDPs[id]?.lastCheck || Date.now() - Math.floor(Math.random() * 21600000 /* 6 hours */), // randomize lastCheck to avoid all datapoints to be checked at same timepoint + } as SQLPointConfig; + + // changesRelogInterval + if (this.sqlDPs[id].config.changesOnly && this.sqlDPs[id].config.changesRelogInterval > 0) { + this.sqlDPs[id].relogTimeout = setTimeout( + (_id: string) => this.reLogHelper(_id), + this.sqlDPs[id].config.changesRelogInterval * 500 * (1 + Math.random()), + id, + ); + } + + if (writeNull && this.config.writeNulls) { + this.writeNulls(id); + } + + this.log.info(`enabled logging of ${id}, Alias=${id !== realId}, WriteNulls=${writeNull}`); + } + + setConnected(isConnected: boolean): void { + if (this.sqlConnected !== isConnected) { + this.sqlConnected = isConnected; + void this.setState('info.connection', this.sqlConnected, true); + } + } + + connect(callback: (err?: Error | null) => void): void { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = null; + } + + if (!this.clientPool) { + this.setConnected(false); + let sqLiteOptions: SQLite3Options | undefined; + let msSQLOptions: MSSQLOptions | undefined; + let mySQLOptions: MySQLOptions | undefined; + let postgreSQLOptions: PostgreSQLOptions | undefined; + + const poolOptions: PoolConfig = { + max_idle: this.config.dbtype === 'sqlite' ? 1 : 2, + }; + + if (this.config.maxConnections) { + poolOptions.max_active = this.config.maxConnections + 1; // we use our own Pool limiter logic, so let library have one more to not block us too early + poolOptions.max_wait = 10000; // hard code for now + poolOptions.when_exhausted = 'block'; + } + + if (!this.config.dbtype) { + return this.log.error('DB Type is not defined!'); + } + if (!clients[this.config.dbtype]) { + return this.log.error(`Unknown type "${this.config.dbtype}"`); + } + + if (this.config.dbtype === 'postgresql') { + postgreSQLOptions = { + host: this.config.host, + user: this.config.user || '', + password: this.config.password || '', + port: this.config.port || undefined, + database: 'postgres', + ssl: this.config.encrypt + ? { + rejectUnauthorized: !!this.config.rejectUnauthorized, + } + : undefined, + }; + } else if (this.config.dbtype === 'mssql') { + msSQLOptions = { + server: this.config.host, // needed for MSSQL + user: this.config.user || '', + password: this.config.password || '', + port: this.config.port || undefined, + options: { + encrypt: !!this.config.encrypt, + trustServerCertificate: !this.config.rejectUnauthorized, + }, + }; + } else if (this.config.dbtype === 'mysql') { + mySQLOptions = { + host: this.config.host, // needed for PostgreSQL , MySQL + user: this.config.user || '', + password: this.config.password || '', + port: this.config.port || undefined, + ssl: this.config.encrypt + ? { + rejectUnauthorized: !!this.config.rejectUnauthorized, + } + : undefined, + }; + } else if (this.config.dbtype === 'sqlite') { + sqLiteOptions = { fileName: this.getSqlLiteDir(this.config.fileName) }; + } + + if (this.config.dbtype === 'postgresql' && !this.postgresDbCreated && postgreSQLOptions) { + // special solution for postgres. Connect first to Db "postgres", create new DB "iobroker" and then connect to "iobroker" DB. + // connect first to DB postgres and create iobroker DB + this.log.info( + `Postgres connection options: ${JSON.stringify(postgreSQLOptions).replace((postgreSQLOptions.password as string) || '******', '****')}`, + ); + const _client: SQLClient = new PostgreSQLClient(postgreSQLOptions); + _client.on?.('error', (err: Error | null): void => this.log.warn(`SQL client error: ${err}`)); + + return _client.connect((err?: Error | null): void => { + if (err) { + this.log.error(err.toString()); + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + } + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + return; + } + + if (this.config.doNotCreateDatabase) { + _client.disconnect(); + this.postgresDbCreated = true; + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 100); + } else { + _client.execute(`CREATE DATABASE ${this.config.dbname};`, (err: Error | null): void => { + _client.disconnect(); + const typedError: { + code: string; + } = err as any; + if (typedError && typedError.code !== '42P04') { + // if error not about yet exists + this.postgresDbCreated = false; + this.log.error(JSON.stringify(typedError)); + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + } else { + // remember that DB is created + this.postgresDbCreated = true; + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 100); + } + }); + } + }); + } + + if (this.config.dbtype === 'postgresql' && postgreSQLOptions) { + postgreSQLOptions.database = this.config.dbname; + } + + try { + if (this.config.dbtype === 'mssql' && msSQLOptions) { + this.clientPool = new MSSQLClientPool(poolOptions, msSQLOptions); + } else if (this.config.dbtype === 'mysql' && mySQLOptions) { + this.clientPool = new MySQL2ClientPool(poolOptions, mySQLOptions); + } else if (this.config.dbtype === 'sqlite' && sqLiteOptions) { + this.clientPool = new SQLite3ClientPool(poolOptions, sqLiteOptions); + } else if (this.config.dbtype === 'postgresql' && postgreSQLOptions) { + this.clientPool = new PostgreSQLClientPool(poolOptions, postgreSQLOptions); + } else { + throw new Error('DB connection options not defined'); + } + + return this.clientPool.open(poolOptions, (err?: Error | null): void => { + this.activeConnections = 0; + if (err) { + this.clientPool = null; + this.setConnected(false); + this.log.error(JSON.stringify(err)); + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + } else { + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + } + setImmediate(() => this.connect(callback)); + } + }); + } catch (ex) { + if (ex.toString() === 'TypeError: undefined is not a function') { + this.log.error(`Node.js DB driver for "${this.config.dbtype}" could not be installed.`); + } else { + this.log.error(ex.toString()); + this.log.error(ex.stack); + } + this.clientPool = null; + this.activeConnections = 0; + this.setConnected(false); + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + return; + } + } + + this.allScripts(this.sqlFuncs!.init(this.config.dbname, this.config.doNotCreateDatabase), 0, err => { + if (err) { + //this.log.error(err); + this.reconnectTimeout && clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = setTimeout(() => { + this.reconnectTimeout = null; + this.connect(callback); + }, 30000); + } else { + this.log.info(`Connected to ${this.config.dbtype}`); + // read all DB IDs and all FROM ids + this.getAllIds(() => this.getAllFroms(callback)); + } + }); + } + + // Find sqlite data directory + getSqlLiteDir(fileName: string): string { + fileName ||= 'sqlite.db'; + fileName = fileName.replace(/\\/g, '/'); + if (fileName[0] === '/' || fileName.match(/^\w:\//)) { + return fileName; + } + // normally /opt/iobroker/node_modules/iobroker.js-controller + // but can be /example/ioBroker.js-controller + const config = join(getAbsoluteDefaultDataDir(), 'sqlite'); + + // create sqlite directory + if (!existsSync(config)) { + mkdirSync(config); + } + + return normalize(join(config, fileName)); + } + + async testConnection(msg: ioBroker.Message): Promise { + if (!msg?.message?.config) { + if (msg.callback) { + this.sendTo(msg.from, msg.command, { error: 'invalid config' }, msg.callback); + } + return; + } + let sqLiteOptions: SQLite3Options | undefined; + let msSQLOptions: MSSQLOptions | undefined; + let mySQLOptions: MySQLOptions | undefined; + let postgreSQLOptions: PostgreSQLOptions | undefined; + + const config: SqlAdapterConfigTyped = msg.message.config; + + config.port = parseInt(config.port as unknown as string, 10) || 0; + + let dockerCreated = false; + let dockerManager: DockerManagerOfOwnContainers | undefined; + if (config.dockerMysql?.enabled) { + // Start docker container if not running and then stop it + const mysqlDockerConfig: ContainerConfig = this.getDockerConfigMySQL(config); + dockerManager = this.getPluginInstance('docker')?.getDockerManager(); + if (!dockerManager) { + dockerCreated = true; + mysqlDockerConfig.removeOnExit = true; + dockerManager = new DockerManagerOfOwnContainers( + { + logger: { + level: 'silly', + silly: this.log.silly.bind(this.log), + debug: this.log.debug.bind(this.log), + info: this.log.info.bind(this.log), + warn: this.log.warn.bind(this.log), + error: this.log.error.bind(this.log), + }, + namespace: this.namespace, + adapterDir: `${__dirname}/../`, + }, + [mysqlDockerConfig], + ); + } + } + + if (config.dbtype === 'postgresql') { + postgreSQLOptions = { + host: config.host, + user: config.user || '', + password: config.password || '', + port: config.port || undefined, + database: 'postgres', + ssl: config.encrypt + ? { + rejectUnauthorized: !!config.rejectUnauthorized, + } + : undefined, + }; + } else if (config.dbtype === 'mssql') { + msSQLOptions = { + server: config.host, // needed for MSSQL + user: config.user || '', + password: config.password || '', + port: config.port || undefined, + options: { + encrypt: !!config.encrypt, + trustServerCertificate: !config.rejectUnauthorized, + }, + }; + } else if (config.dbtype === 'mysql') { + mySQLOptions = { + host: config.host, // needed for PostgreSQL , MySQL + user: config.user || '', + password: config.password || '', + port: config.port || undefined, + ssl: config.encrypt + ? { + rejectUnauthorized: !!config.rejectUnauthorized, + } + : undefined, + }; + } else if (config.dbtype === 'sqlite') { + sqLiteOptions = { fileName: this.getSqlLiteDir(config.fileName) }; + } + + try { + let client: SQLClient | undefined; + if (config.dbtype === 'postgresql' && postgreSQLOptions) { + client = new PostgreSQLClient(postgreSQLOptions); + } else if (config.dbtype === 'mssql' && msSQLOptions) { + client = new MSSQLClient(msSQLOptions); + } else if (config.dbtype === 'mysql' && mySQLOptions) { + client = new MySQL2Client(mySQLOptions); + } else if (config.dbtype === 'sqlite' && sqLiteOptions) { + client = new SQLite3Client(sqLiteOptions); + } else { + this.sendTo(msg.from, msg.command, { error: 'Unknown DB type' }, msg.callback); + return; + } + client.on?.('error', (err?: string): void => this.log.warn(`SQL client error: ${err}`)); + + this.testConnectTimeout = setTimeout(() => { + this.testConnectTimeout = null; + this.sendTo(msg.from, msg.command, { error: 'connect timeout' }, msg.callback); + }, 5000); + + const err = await new Promise(resolve => + client.connect((err?: Error | null): void => { + if (err) { + if (this.testConnectTimeout) { + clearTimeout(this.testConnectTimeout); + this.testConnectTimeout = null; + } + this.sendTo( + msg.from, + msg.command, + { error: `${(err as any).code} ${err.toString()}` }, + msg.callback, + ); + return; + } + + client.execute('SELECT 2 + 3 AS x', (err?: Error | null /* , rows, fields */): void => { + client.disconnect(); + resolve(err); + }); + }), + ); + + if (dockerCreated && dockerManager) { + try { + await dockerManager.destroy(); + dockerManager = undefined; + dockerCreated = false; + } catch (e) { + this.log.error(`Cannot stop docker container: ${e}`); + } + } + if (this.testConnectTimeout) { + clearTimeout(this.testConnectTimeout); + this.testConnectTimeout = null; + this.sendTo(msg.from, msg.command, { error: err?.toString() || null }, msg.callback); + return; + } + } catch (ex) { + if (this.testConnectTimeout) { + clearTimeout(this.testConnectTimeout); + this.testConnectTimeout = null; + } + if (dockerCreated && dockerManager) { + try { + await dockerManager.destroy(); + } catch (e) { + this.log.error(`Cannot stop docker container: ${e}`); + } + } + if (ex.toString() === 'TypeError: undefined is not a function') { + this.sendTo( + msg.from, + msg.command, + { error: 'Node.js DB driver could not be installed.' }, + msg.callback, + ); + } else { + this.sendTo(msg.from, msg.command, { error: ex.toString() }, msg.callback); + } + } + } + + destroyDB(msg: ioBroker.Message): void { + try { + this.allScripts(this.sqlFuncs!.destroy(this.config.dbname), 0, err => { + if (err) { + this.log.error(err.toString()); + this.sendTo(msg.from, msg.command, { error: err.toString() }, msg.callback); + } else { + this.sendTo(msg.from, msg.command, { error: null, result: 'deleted' }, msg.callback); + // restart adapter + setTimeout( + () => + this.getForeignObject(`system.adapter.${this.namespace}`, (err, obj) => { + if (!err && obj) { + void this.setForeignObject(obj._id, obj); + } else { + this.log.error( + `Cannot read object "system.adapter.${this.namespace}": ${err || 'Config does not exist'}`, + ); + this.stop ? void this.stop() : void this.terminate(); + } + }), + 2000, + ); + } + }); + } catch (ex) { + return this.sendTo(msg.from, msg.command, { error: ex.toString() }, msg.callback); + } + } + + _userQuery(msg: ioBroker.Message, callback?: () => void): void { + try { + if (typeof msg.message !== 'string' || !msg.message.length) { + throw new Error('No query provided'); + } + this.log.debug(msg.message); + + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.sendTo(msg.from, msg.command, { error: err?.toString() || 'No client' }, msg.callback); + this.returnClientToPool(client); + callback?.(); + } else { + client.execute(msg.message, (err, rows /* , fields */) => { + this.returnClientToPool(client); + //convert ts for postgresql and ms sqlserver + if (!err && rows?.[0] && typeof rows[0].ts === 'string') { + for (let i = 0; i < rows.length; i++) { + rows[i].ts = parseInt(rows[i].ts, 10); + } + } + this.sendTo( + msg.from, + msg.command, + { error: err ? err.toString() : null, result: rows }, + msg.callback, + ); + callback?.(); + }); + } + }); + } catch (err) { + this.sendTo(msg.from, msg.command, { error: err.toString() }, msg.callback); + callback?.(); + } + } + + // execute custom query + query(msg: ioBroker.Message): void { + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + this.sendTo(msg.from, msg.command, { error }, msg.callback); + } else { + this.tasks.push({ operation: 'userQuery', msg }); + if (this.tasks.length === 1) { + this.processTasks(); + } + } + } else { + this._userQuery(msg); + } + } + + // one script + oneScript(script: string, cb?: (err?: Error | null) => void): void { + try { + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.clientPool?.close(); + this.activeConnections = 0; + this.clientPool = null; + this.setConnected(false); + this.log.error(err?.toString() || 'No database connection'); + return cb?.(err || new Error('No database connection')); + } + + this.log.debug(script); + + client.execute(script, (err?: Error | null): void => { + this.log.debug(`Response: ${JSON.stringify(err)}`); + if (err) { + const typedError: { + number?: number; + errno?: number; + code?: string; + message?: string; + } = err as any; + // Database 'iobroker' already exists. Choose a different database name. + if ( + typedError.number === 1801 || + // There is already an object named 'sources' in the database. + typedError.number === 2714 + ) { + // do nothing + err = null; + } else if (typedError.message?.match(/^SQLITE_ERROR: table [\w_]+ already exists$/)) { + // do nothing + err = null; + } else if (typedError.errno == 1007 || typedError.errno == 1050) { + // if a database exists or table exists, + // do nothing + err = null; + } else if (typedError.code === '42P04') { + // if a database exists or table exists, + // do nothing + err = null; + } else if (typedError.code === '42P07') { + const match = script.match(/CREATE\s+TABLE\s+(\w*)\s+\(/); + if (match) { + this.log.debug(`OK. Table "${match[1]}" yet exists`); + err = null; + } else { + this.log.error(script); + this.log.error(err.toString()); + } + } else if (script.startsWith('CREATE INDEX')) { + this.log.info('Ignore Error on Create index. You might want to create the index yourself!'); + this.log.info(script); + this.log.info(`${typedError.code}: ${err}`); + err = null; + } else { + this.log.error(script); + this.log.error(err.toString()); + } + } + this.returnClientToPool(client); + cb?.(err); + }); + }); + } catch (ex) { + this.log.error(ex); + cb?.(ex); + } + } + + // all scripts + allScripts(scripts: string[], index: number, cb: (err?: Error | null) => void): void { + index ||= 0; + + if (scripts && index < scripts.length) { + this.oneScript(scripts[index], err => { + if (err) { + cb?.(err); + } else { + this.allScripts(scripts, index + 1, cb); + } + }); + } else { + cb?.(); + } + } + + finish(callback: () => void): void { + let count = 0; + const now = Date.now(); + + const allFinished = (): void => { + if (this.clientPool) { + this.clientPool.close(); + this.activeConnections = 0; + this.clientPool = null; + this.setConnected(false); + } + if (typeof this.finished === 'object') { + setTimeout( + (cb: (() => void)[]): void => { + for (let f = 0; f < cb.length; f++) { + typeof cb[f] === 'function' && cb[f](); + } + }, + 500, + this.finished, + ); + this.finished = true; + } + }; + + const finishId = (id: string): void => { + if (!this.sqlDPs[id]) { + return; + } + if (this.sqlDPs[id].relogTimeout) { + clearTimeout(this.sqlDPs[id].relogTimeout); + this.sqlDPs[id].relogTimeout = null; + } + if (this.sqlDPs[id].timeout) { + clearTimeout(this.sqlDPs[id].timeout); + this.sqlDPs[id].timeout = null; + } + const state: IobDataEntryEx | null = this.sqlDPs[id].state ? { ...this.sqlDPs[id].state } : null; + + if ( + this.sqlDPs[id].skipped && + !(this.sqlDPs[id].config && this.sqlDPs[id].config.disableSkippedValueLogging) + ) { + count++; + this.pushValueIntoDB(id, this.sqlDPs[id].skipped, false, true, () => { + if (!--count) { + this.pushValuesIntoDB(id, this.sqlDPs[id].list, () => { + allFinished(); + }); + } + }); + this.sqlDPs[id].skipped = null; + } + + const nullValue: IobDataEntryEx = { + ack: true, + val: null, + ts: now, + lc: now, + q: 0x40, + from: `system.adapter.${this.namespace}`, + }; + + if (this.sqlDPs[id].config && this.config.writeNulls) { + if (this.sqlDPs[id].config.changesOnly && state && state.val !== null) { + count++; + ((_id: string, _state: IobDataEntryEx, _nullValue: IobDataEntryEx): void => { + _state.ts = now; + _state.from = `system.adapter.${this.namespace}`; + nullValue.ts += 4; + nullValue.lc += 4; // because of MS SQL + this.log.debug(`Write 1/2 "${_state.val}" _id: ${_id}`); + this.pushValueIntoDB(_id, _state, false, true, () => { + // terminate values with null to indicate adapter stop. timestamp + 1 + this.log.debug(`Write 2/2 "null" _id: ${_id}`); + this.pushValueIntoDB(_id, _nullValue, false, true, () => { + if (!--count) { + this.pushValuesIntoDB(id, this.sqlDPs[id].list, () => { + allFinished(); + }); + } + }); + }); + })(id, state, nullValue); + } else { + // terminate values with null to indicate adapter stop. timestamp + 1 + count++; + this.log.debug(`Write 0 NULL _id: ${id}`); + this.pushValueIntoDB(id, nullValue, false, true, () => { + if (!--count) { + this.pushValuesIntoDB(id, this.sqlDPs[id].list, () => { + allFinished(); + }); + } + }); + } + } + }; + + if (!this.subscribeAll) { + for (const _id in this.sqlDPs) { + if ( + Object.prototype.hasOwnProperty.call(this.sqlDPs, _id) && + Object.prototype.hasOwnProperty.call(this.sqlDPs, this.sqlDPs[_id].realId) + ) { + this.unsubscribeForeignStates(this.sqlDPs[_id].realId); + } + } + } else { + this.subscribeAll = false; + this.unsubscribeForeignStates('*'); + } + + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + this.reconnectTimeout = null; + } + if (this.testConnectTimeout) { + clearTimeout(this.testConnectTimeout); + this.testConnectTimeout = null; + } + if (this.dpOverviewTimeout) { + clearTimeout(this.dpOverviewTimeout); + this.dpOverviewTimeout = null; + } + if (this.bufferChecker) { + clearInterval(this.bufferChecker); + this.bufferChecker = null; + } + + if (this.finished) { + if (callback) { + if (this.finished === true) { + callback(); + } else { + this.finished.push(callback); + } + } + return; + } + this.finished = [callback]; + let dpcount = 0; + let delay = 0; + for (const id in this.sqlDPs) { + if (!Object.prototype.hasOwnProperty.call(this.sqlDPs, id)) { + continue; + } + dpcount++; + delay += dpcount % 50 === 0 ? 1000 : 0; + setTimeout(finishId, delay, id); + } + + if (!dpcount && callback) { + if (this.clientPool) { + this.clientPool.close(); + this.activeConnections = 0; + this.clientPool = null; + this.setConnected(false); + } + callback(); + } + } + + processMessage(msg: ioBroker.Message): void { + if (msg.command === 'features') { + this.sendTo( + msg.from, + msg.command, + { supportedFeatures: ['update', 'delete', 'deleteRange', 'deleteAll', 'storeState'] }, + msg.callback, + ); + } else if (msg.command === 'getHistory') { + this.getHistorySql(msg); + } else if (msg.command === 'getCounter') { + this.getCounterDiff(msg); + } else if (msg.command === 'test') { + void this.testConnection(msg); + } else if (msg.command === 'destroy') { + this.destroyDB(msg); + } else if (msg.command === 'query') { + this.query(msg); + } else if (msg.command === 'update') { + this.updateState(msg); + } else if (msg.command === 'delete') { + this.deleteHistoryEntry(msg); + } else if (msg.command === 'deleteAll') { + this.deleteStateAll(msg); + } else if (msg.command === 'deleteRange') { + this.deleteHistoryEntry(msg); + } else if (msg.command === 'storeState') { + this.storeState(msg).catch(e => this.log.error(`Cannot store state: ${e}`)); + } else if (msg.command === 'getDpOverview') { + this.getDpOverview(msg); + } else if (msg.command === 'enableHistory') { + this.enableHistory(msg); + } else if (msg.command === 'disableHistory') { + this.disableHistory(msg); + } else if (msg.command === 'getEnabledDPs') { + this.getEnabledDPs(msg); + } else if (msg.command === 'stopInstance') { + this.finish(() => { + if (msg.callback) { + this.sendTo(msg.from, msg.command, 'stopped', msg.callback); + setTimeout(() => (this.stop ? this.stop() : this.terminate?.()), 200); + } + }); + } + } + + processStartValues(callback?: () => void): void { + if (this.tasksStart?.length) { + const task = this.tasksStart.shift()!; + const sqlDP = this.sqlDPs[task.id]; + if (sqlDP.config.changesOnly) { + void this.getForeignState(sqlDP.realId, (err, state) => { + const now = task.now || Date.now(); + this.pushHistory(task.id, { + val: null, + ts: state ? now - 4 : now, // 4 is because of MS SQL + lc: state ? now - 4 : now, // 4 is because of MS SQL + ack: true, + q: 0x40, + from: `system.adapter.${this.namespace}`, + }); + + if (state) { + state.ts = now; + state.lc = now; + state.from = `system.adapter.${this.namespace}`; + this.pushHistory(task.id, state as IobDataEntryEx); + } + + setImmediate(() => this.processStartValues()); + }); + } else { + const now = Date.now(); + this.pushHistory(task.id, { + val: null, + ts: task.now || now, + lc: task.now || now, + ack: true, + q: 0x40, + from: `system.adapter.${this.namespace}`, + }); + + setImmediate(() => this.processStartValues()); + } + if (sqlDP.config?.changesOnly && sqlDP.config.changesRelogInterval > 0) { + if (sqlDP.relogTimeout) { + clearTimeout(sqlDP.relogTimeout); + } + sqlDP.relogTimeout = setTimeout( + (id: string) => this.reLogHelper(id), + sqlDP.config.changesRelogInterval * 500 * Math.random() + sqlDP.config.changesRelogInterval * 500, + task.id, + ); + } + } else { + callback && callback(); + } + } + + writeNulls(id?: string, now?: number): void { + if (!id) { + now = Date.now(); + + Object.keys(this.sqlDPs) + .filter(_id => this.sqlDPs[_id] && this.sqlDPs[_id].config) + .forEach(_id => this.writeNulls(_id, now)); + } else { + now ||= Date.now(); + + this.tasksStart.push({ id, now }); + + if (this.tasksStart.length === 1 && this.sqlConnected) { + this.processStartValues(); + } + } + } + + pushHistory(id: string, state: IobDataEntryEx | null | undefined, timerRelog?: boolean): void { + timerRelog ||= false; + + // Push into DB + if (this.sqlDPs[id]) { + const settings = this.sqlDPs[id].config; + + if (!settings || !state) { + return; + } + + if (state && state.val === undefined) { + return this.log.warn(`state value undefined received for ${id} which is not allowed. Ignoring.`); + } + + if (typeof state.val === 'string' && settings.storageType !== 'String') { + if (isFinite(state.val as any)) { + state.val = parseFloat(state.val); + } + } + + if (settings.enableDebugLogs) { + this.log.debug( + `new value received for ${id} (storageType ${settings.storageType}), new-value=${state.val}, ts=${state.ts}, relog=${timerRelog}`, + ); + } + + let ignoreDebounce = false; + + if (!timerRelog) { + const valueUnstable = !!this.sqlDPs[id].timeout; + // When a debounce timer runs and the value is the same as the last one, ignore it + if (this.sqlDPs[id].timeout && state.ts !== state.lc) { + settings.enableDebugLogs && + this.log.debug( + `value not changed debounce ${id}, value=${state.val}, ts=${state.ts}, debounce timer keeps running`, + ); + return; + } else if (this.sqlDPs[id].timeout) { + // if value changed, clear timer + settings.enableDebugLogs && + this.log.debug( + `value changed during debounce time ${id}, value=${state.val}, ts=${state.ts}, debounce timer restarted`, + ); + clearTimeout(this.sqlDPs[id].timeout); + this.sqlDPs[id].timeout = null; + } + + if ( + !valueUnstable && + settings.blockTime && + this.sqlDPs[id].state && + this.sqlDPs[id].state.ts + settings.blockTime > state.ts + ) { + settings.enableDebugLogs && + this.log.debug( + `value ignored blockTime ${id}, value=${state.val}, ts=${state.ts}, lastState.ts=${this.sqlDPs[id].state.ts}, blockTime=${settings.blockTime}`, + ); + return; + } + + if (settings.ignoreZero && (state.val === undefined || state.val === null || state.val === 0)) { + if (settings.enableDebugLogs) { + this.log.debug( + `value ignore because zero or null ${id}, new-value=${state.val}, ts=${state.ts}`, + ); + } + return; + } + if ( + typeof settings.ignoreBelowNumber === 'number' && + typeof state.val === 'number' && + state.val < settings.ignoreBelowNumber + ) { + if (settings.enableDebugLogs) { + this.log.debug( + `value ignored because below ${settings.ignoreBelowNumber} for ${id}, new-value=${state.val}, ts=${state.ts}`, + ); + } + return; + } + if ( + typeof settings.ignoreAboveNumber === 'number' && + typeof state.val === 'number' && + state.val > settings.ignoreAboveNumber + ) { + if (settings.enableDebugLogs) { + this.log.debug( + `value ignored because above ${settings.ignoreAboveNumber} for ${id}, new-value=${state.val}, ts=${state.ts}`, + ); + } + return; + } + + if (this.sqlDPs[id].state && settings.changesOnly) { + if (!settings.changesRelogInterval) { + if ((this.sqlDPs[id].state.val !== null || state.val === null) && state.ts !== state.lc) { + // remember new timestamp + if (!valueUnstable && !settings.disableSkippedValueLogging) { + this.sqlDPs[id].skipped = state; + } + settings.enableDebugLogs && + this.log.debug( + `value not changed ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`, + ); + return; + } + } else if (this.sqlDPs[id].lastLogTime) { + if ( + (this.sqlDPs[id].state.val !== null || state.val === null) && + state.ts !== state.lc && + Math.abs(this.sqlDPs[id].lastLogTime - state.ts) < settings.changesRelogInterval * 1000 + ) { + // remember new timestamp + if (!valueUnstable && !settings.disableSkippedValueLogging) { + this.sqlDPs[id].skipped = state; + } + settings.enableDebugLogs && + this.log.debug( + `value not changed ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`, + ); + return; + } + if (state.ts !== state.lc) { + settings.enableDebugLogs && + this.log.debug( + `value-not-changed-relog ${id}, value=${state.val}, lastLogTime=${this.sqlDPs[id].lastLogTime}, ts=${state.ts}`, + ); + ignoreDebounce = true; + } + } + if (typeof state.val === 'number') { + if ( + this.sqlDPs[id].state.val !== null && + settings.changesMinDelta && + Math.abs((this.sqlDPs[id].state.val as number) - state.val) < settings.changesMinDelta + ) { + if (!valueUnstable && !settings.disableSkippedValueLogging) { + this.sqlDPs[id].skipped = state; + } + if (settings.enableDebugLogs) { + this.log.debug( + `Min-Delta not reached ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`, + ); + } + return; + } + if (settings.changesMinDelta && settings.enableDebugLogs) { + this.log.debug( + `Min-Delta reached ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`, + ); + } + } else if (settings.enableDebugLogs) { + this.log.debug( + `Min-Delta ignored because no number ${id}, last-value=${this.sqlDPs[id].state.val}, new-value=${state.val}, ts=${state.ts}`, + ); + } + } + } + + if (settings.counter && this.sqlDPs[id].state) { + if (this.sqlDPs[id].type !== types.number) { + this.log.error('Counter must have type "number"!'); + } else if ( + state.val === null || + this.sqlDPs[id].state.val === null || + (state.val as unknown as number) < (this.sqlDPs[id].state.val as unknown as number) + ) { + // if the actual value is less then last seen counter, store both values + this.pushValueIntoDB(id, this.sqlDPs[id].state, true); + this.pushValueIntoDB(id, state, true); + } + } + + if (this.sqlDPs[id].relogTimeout) { + clearTimeout(this.sqlDPs[id].relogTimeout); + this.sqlDPs[id].relogTimeout = null; + } + + if (timerRelog) { + state = { ...state }; + state.ts = Date.now(); + state.from = `system.adapter.${this.namespace}`; + settings.enableDebugLogs && + this.log.debug( + `timed-relog ${id}, value=${state.val}, lastLogTime=${this.sqlDPs[id].lastLogTime}, ts=${state.ts}`, + ); + ignoreDebounce = true; + } else { + if (settings.changesOnly && this.sqlDPs[id].skipped) { + settings.enableDebugLogs && + this.log.debug( + `Skipped value logged ${id}, value=${this.sqlDPs[id].skipped.val}, ts=${this.sqlDPs[id].skipped.ts}`, + ); + this.pushHelper(id, this.sqlDPs[id].skipped); + this.sqlDPs[id].skipped = null; + } + if ( + this.sqlDPs[id].state && + ((this.sqlDPs[id].state.val === null && state.val !== null) || + (this.sqlDPs[id].state.val !== null && state.val === null)) + ) { + ignoreDebounce = true; + } else if (!this.sqlDPs[id].state && state.val === null) { + ignoreDebounce = true; + } + } + + if (settings.debounceTime && !ignoreDebounce && !timerRelog) { + // Discard changes in the debounce time to store last stable value + this.sqlDPs[id].timeout && clearTimeout(this.sqlDPs[id].timeout); + this.sqlDPs[id].timeout = setTimeout( + (id, state) => { + if (!this.sqlDPs[id]) { + return; + } + this.sqlDPs[id].timeout = null; + this.sqlDPs[id].state = state; + this.sqlDPs[id].lastLogTime = state.ts; + if (settings.enableDebugLogs) { + this.log.debug( + `Value logged ${id}, value=${this.sqlDPs[id].state.val}, ts=${this.sqlDPs[id].state.ts}`, + ); + } + this.pushHelper(id); + if (settings.changesOnly && settings.changesRelogInterval > 0) { + this.sqlDPs[id].relogTimeout = setTimeout( + (_id: string) => this.reLogHelper(_id), + settings.changesRelogInterval * 1000, + id, + ); + } + }, + settings.debounceTime, + id, + state, + ); + } else { + if (!timerRelog) { + this.sqlDPs[id].state = state; + } + this.sqlDPs[id].lastLogTime = state.ts; + + if (settings.enableDebugLogs) { + this.log.debug( + `Value logged ${id}, value=${this.sqlDPs[id].state?.val}, ts=${this.sqlDPs[id].state?.ts}`, + ); + } + + this.pushHelper(id, state); + + if (settings.changesOnly && settings.changesRelogInterval > 0) { + this.sqlDPs[id].relogTimeout = setTimeout( + (_id: string) => this.reLogHelper(_id), + settings.changesRelogInterval * 1000, + id, + ); + } + } + } + } + + reLogHelper(_id: string): void { + if (!this.sqlDPs[_id]) { + this.log.info(`non-existing id ${_id}`); + } else { + this.sqlDPs[_id].relogTimeout = null; + if (this.sqlDPs[_id].skipped) { + this.pushHistory(_id, this.sqlDPs[_id].skipped, true); + } else if (this.sqlDPs[_id].state) { + this.pushHistory(_id, this.sqlDPs[_id].state, true); + } else { + void this.getForeignState(this.sqlDPs[_id].realId, (err, state) => { + if (err) { + this.log.info(`init timed Relog: can not get State for ${_id} : ${err}`); + } else if (!state) { + this.log.info( + `init timed Relog: disable relog because state not set so far for ${_id}: ${JSON.stringify(state)}`, + ); + } else { + this.log.debug( + `init timed Relog: getState ${_id}: Value=${state.val}, ack=${state.ack}, ts=${state.ts}, lc=${state.lc}`, + ); + this.sqlDPs[_id].state = state; + this.pushHistory(_id, this.sqlDPs[_id].state, true); + } + }); + } + } + } + + pushHelper(_id: string, state?: IobDataEntryEx, cb?: (err?: Error | null) => void): void { + if (!this.sqlDPs[_id] || (!this.sqlDPs[_id].state && !state)) { + return; + } + state ||= this.sqlDPs[_id].state!; + + const _settings = this.sqlDPs[_id].config || {}; + + let val: ioBroker.StateValue = state.val; + if (val !== null && (typeof val === 'object' || typeof val === 'undefined')) { + val = JSON.stringify(val); + } + + if (val !== null && val !== undefined) { + if (_settings.enableDebugLogs) { + this.log.debug(`Datatype ${_id}: Currently: ${typeof val}, StorageType: ${_settings.storageType}`); + } + + if (typeof val === 'string' && _settings.storageType !== 'String') { + _settings.enableDebugLogs && this.log.debug(`Do Automatic Datatype conversion for ${_id}`); + if (isFinite(val as any)) { + val = parseFloat(val); + } else if (val === 'true') { + val = true; + } else if (val === 'false') { + val = false; + } + } + + if (_settings.storageType === 'String' && typeof val !== 'string') { + val = val.toString(); + } else if (_settings.storageType === 'Number' && typeof val !== 'number') { + if (typeof val === 'boolean') { + val = val ? 1 : 0; + } else { + return this.log.info(`Do not store value "${val}" for ${_id} because no number`); + } + } else if (_settings.storageType === 'Boolean' && typeof val !== 'boolean') { + val = !!val; + } + } else { + _settings.enableDebugLogs && this.log.debug(`Datatype ${_id}: Currently: null`); + } + + state.val = val; + + this.pushValueIntoDB(_id, state, false, false, cb); + } + + getAllIds(cb: (err?: Error | null) => void): void { + const query = this.sqlFuncs!.getIdSelect(this.config.dbname); + this.log.debug(query); + + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + return cb?.(err || new Error('No client')); + } + + client.execute<{ name: string; id: number; type: 0 | 1 | 2 }>( + query, + (err: Error | null | undefined, rows?: { name: string; id: number; type: 0 | 1 | 2 }[]): void => { + this.returnClientToPool(client); + if (err) { + this.log.error(`Cannot select ${query}: ${err}`); + return cb?.(err); + } + + if (rows?.length) { + let id; + for (let r = 0; r < rows.length; r++) { + id = rows[r].name; + this.sqlDPs[id] ||= {} as SQLPointConfig; + this.sqlDPs[id].index = rows[r].id; + if (rows[r].type !== null) { + this.sqlDPs[id].dbType = rows[r].type; + } + } + } + cb?.(); + }, + ); + }); + } + + getAllFroms(cb: (err?: Error | null) => void): void { + const query = this.sqlFuncs!.getFromSelect(this.config.dbname); + this.log.debug(query); + + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + return cb?.(err || new Error('No client')); + } + + client.execute<{ name: string; id: number }>(query, (err, rows /* , fields */) => { + this.returnClientToPool(client); + if (err) { + this.log.error(`Cannot select ${query}: ${err}`); + return cb?.(err); + } + + if (rows?.length) { + for (let r = 0; r < rows.length; r++) { + this.from[rows[r].name] = rows[r].id; + } + } + + cb?.(); + }); + }); + } + + _checkRetention(query: string, cb?: () => void): void { + this.log.debug(query); + + this.borrowClientFromPool((err?: Error | null, client?: SQLClient): void => { + if (err || !client) { + this.returnClientToPool(client); + this.log.error(err?.toString() || 'No client'); + cb?.(); + } else { + client.execute(query, (err?: Error | null): void => { + this.returnClientToPool(client); + if (err) { + this.log.warn(`Retention: Cannot delete ${query}: ${err}`); + } + cb?.(); + }); + } + }); + } + + checkRetention(id: string): void { + if (this.sqlDPs[id]?.config?.retention) { + const dt = Date.now(); + // check every 6 hours + if (!this.sqlDPs[id].lastCheck || dt - this.sqlDPs[id].lastCheck >= 21600000 /* 6 hours */) { + this.sqlDPs[id].lastCheck = dt; + + if (!dbNames[this.sqlDPs[id].type]) { + this.log.error(`No type ${this.sqlDPs[id].type} found for ${id}. Retention is not possible.`); + } else { + const query = this.sqlFuncs!.retention( + this.config.dbname, + this.sqlDPs[id].index, + dbNames[this.sqlDPs[id].type], + this.sqlDPs[id].config.retention, + ); + + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + return this.log.error(`Cannot queue new requests, because more than ${MAX_TASKS}`); + } + + const start = this.tasks.length === 1; + this.tasks.push({ operation: 'delete', query }); + + // delete counters too + if (this.sqlDPs[id] && this.sqlDPs[id].type === 0) { + // 0 === number + const query = this.sqlFuncs!.retention( + this.config.dbname, + this.sqlDPs[id].index, + 'ts_counter', + this.sqlDPs[id].config.retention, + ); + this.tasks.push({ operation: 'delete', query }); + } + + start && this.processTasks(); + } else { + this._checkRetention(query, () => { + // delete counters too + if (this.sqlDPs[id] && this.sqlDPs[id].type === 0) { + // 0 === number + const query = this.sqlFuncs!.retention( + this.config.dbname, + this.sqlDPs[id].index, + 'ts_counter', + this.sqlDPs[id].config.retention, + ); + this._checkRetention(query); + } + }); + } + } + } + } + } + + _insertValueIntoDB(query: string, id: string, cb?: (err?: Error | null) => void): void { + this.log.debug(query); + + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + this.log.error(err?.toString() || 'No client'); + cb?.(); // BF asked (2021.12.14): may be return here err? + } else { + client.execute(query, (err /* , rows, fields */) => { + this.returnClientToPool(client); + if (err) { + this.log.error(`Cannot insert ${query}: ${err} (id: ${id})`); + } else { + this.checkRetention(id); + } + cb?.(); // BF asked (2021.12.14): may be return here err? + }); + } + }); + } + + _executeQuery(query: string, id: string, cb?: () => void): void { + this.log.debug(query); + + this.borrowClientFromPool((err: Error | null | undefined, client?: SQLClient): void => { + if (err || !client) { + this.returnClientToPool(client); + this.log.error(err?.toString() || 'No client'); + cb?.(); + } else { + client.execute(query, (err?: Error | null): void => { + this.returnClientToPool(client); + if (err) { + this.log.error(`Cannot query ${query}: ${err} (id: ${id})`); + } + cb?.(); + }); + } + }); + } + + processReadTypes(): void { + if (this.tasksReadType?.length) { + const task = this.tasksReadType[0]; + + if (!this.sqlDPs[task.id]) { + this.log.warn(`Ignore type lookup for ${task.id} because not enabled anymore`); + task.cb?.(new Error(`Ignore type lookup for ${task.id} because not enabled anymore`)); + task.cb = null; + setImmediate(() => { + this.tasksReadType.shift(); + this.processReadTypes(); + }); + return; + } + const sqlDP = this.sqlDPs[task.id]; + + this.log.debug( + `Type set in Def for ${task.id}: ${this.sqlDPs[task.id].config && this.sqlDPs[task.id].config.storageType}`, + ); + + if (sqlDP.config?.storageType) { + sqlDP.type = types[sqlDP.config.storageType.toLowerCase()]; + this.log.debug(`Type (from Def) for ${task.id}: ${sqlDP.type}`); + this.processVerifyTypes(task); + } else if (sqlDP.dbType !== undefined) { + sqlDP.type = sqlDP.dbType; + if (sqlDP.config) { + sqlDP.config.storageType = storageTypes[sqlDP.type]; + } + this.log.debug(`Type (from DB-Type) for ${task.id}: ${sqlDP.type}`); + this.processVerifyTypes(task); + } else { + void this.getForeignObject(sqlDP.realId, (err, obj) => { + if (err) { + this.log.warn(`Error while get Object for Def for ${sqlDP.realId}: ${err}`); + } + + if (!sqlDP) { + this.log.warn(`Ignore type lookup for ${task.id} because not enabled anymore`); + task.cb?.(new Error(`Ignore type lookup for ${task.id} because not enabled anymore`)); + task.cb = null; + return setImmediate(() => { + this.tasksReadType.shift(); + this.processReadTypes(); + }); + } else if (obj?.common?.type && types[obj.common.type.toLowerCase()] !== undefined) { + // read type from object + this.log.debug( + `${obj.common.type.toLowerCase()} / ${types[obj.common.type.toLowerCase()]} / ${JSON.stringify(obj.common)}`, + ); + sqlDP.type = types[obj.common.type.toLowerCase() as 'string' | 'number' | 'boolean']; + if (sqlDP.config) { + sqlDP.config.storageType = storageTypes[sqlDP.type]; + } + this.log.debug(`Type (from Obj) for ${task.id}: ${sqlDP.type}`); + this.processVerifyTypes(task); + } else if (sqlDP.type === undefined) { + void this.getForeignState(sqlDP.realId, (err, state) => { + if (!this.sqlDPs[task.id]) { + this.log.warn(`Ignore type lookup for ${task.id} because not enabled anymore`); + task.cb?.(new Error(`Ignore type lookup for ${task.id} because not enabled anymore`)); + task.cb = null; + return setImmediate(() => { + this.tasksReadType.shift(); + this.processReadTypes(); + }); + } + + if (err && task.state) { + this.log.warn( + `Fallback to type of current state value because no other valid type found`, + ); + state = task.state as ioBroker.State; + } + if ( + state && + state.val !== null && + state.val !== undefined && + types[typeof state.val] !== undefined + ) { + this.sqlDPs[task.id].type = types[typeof state.val]; + if (this.sqlDPs[task.id].config) { + this.sqlDPs[task.id].config.storageType = storageTypes[this.sqlDPs[task.id].type]; + } + } else { + this.log.warn( + `Store data for ${task.id} as string because no other valid type found (${state ? typeof state.val : 'state not existing'})`, + ); + this.sqlDPs[task.id].type = 1; // string + } + + this.log.debug(`Type (from State) for ${task.id}: ${this.sqlDPs[task.id].type}`); + this.processVerifyTypes(task); + }); + } else { + // all OK + task.cb?.(); + task.cb = null; + this.tasksReadType.shift(); + this.processReadTypes(); + } + }); + } + } + } + + processVerifyTypes(task: { id: string; state: IobDataEntryEx; cb?: ((err?: Error | null) => void) | null }): void { + if ( + this.sqlDPs[task.id].index !== undefined && + this.sqlDPs[task.id].type !== undefined && + this.sqlDPs[task.id].type !== this.sqlDPs[task.id].dbType + ) { + this.sqlDPs[task.id].dbType = this.sqlDPs[task.id].type; + + const query = this.sqlFuncs!.getIdUpdate( + this.config.dbname, + this.sqlDPs[task.id].index, + this.sqlDPs[task.id].type, + ); + + this.log.debug(query); + + return this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + this.processVerifyTypes(task); + return; + } + + client.execute(query, err => { + this.returnClientToPool(client); + if (err) { + this.log.error( + `error updating history config for ${task.id} to pin datatype: ${query}: ${err}`, + ); + } else { + this.log.info(`changed history configuration to pin detected datatype for ${task.id}`); + } + this.processVerifyTypes(task); + }); + }); + } + + task.cb?.(); + task.cb = null; + + setTimeout(() => { + this.tasksReadType.shift(); + this.processReadTypes(); + }, 50); + } + + prepareTaskReadDbId( + id: string, + state: IobDataEntryEx, + isCounter: boolean, + cb?: (err?: Error | null) => void, + ): void { + if (!this.sqlDPs[id]) { + cb?.(new Error(`${id} not active any more`)); + return; + } + const type = this.sqlDPs[id].type; + + if (type === undefined) { + // Can not happen anymore + let warn; + if (state.val === null) { + warn = `Ignore null value for ${id} because no type defined till now.`; + } else { + warn = `Cannot store values of type "${typeof state.val}" for ${id}`; + } + + this.log.warn(warn); + cb?.(new Error(warn)); + return; + } + + let tmpState: IobDataEntryEx; + // get SQL id of state + if (this.sqlDPs[id].index === undefined) { + this.sqlDPs[id].isRunning ||= []; + + tmpState = { ...state }; + + this.sqlDPs[id].isRunning.push({ id, state: tmpState, cb, isCounter }); + + if (this.sqlDPs[id].isRunning.length === 1) { + // read or create in DB + return this.getId(id, type, (err, _id) => { + this.log.debug( + `prepareTaskCheckTypeAndDbId getId Result - isRunning length = ${this.sqlDPs[id].isRunning ? this.sqlDPs[id].isRunning.length : 'none'}`, + ); + if (err) { + this.log.warn(`Cannot get index of "${_id}": ${err}`); + this.sqlDPs[_id].isRunning?.forEach(r => + r.cb?.(new Error(`Cannot get index of "${r.id}": ${err}`)), + ); + } else { + this.sqlDPs[_id].isRunning?.forEach(r => r.cb?.()); + } + + this.sqlDPs[_id].isRunning = undefined; + }); + } + return; + } + + // get from + if (!isCounter && state.from && !this.from[state.from]) { + this.isFromRunning[state.from] ||= []; + tmpState = { ...state }; + const isFromRunning = this.isFromRunning[state.from]!; + + isFromRunning.push({ id, state: tmpState, cb }); + + if (isFromRunning.length === 1) { + // read or create in DB + return this.getFrom(state.from, (err, from) => { + this.log.debug( + `prepareTaskCheckTypeAndDbId getFrom ${from} Result - isRunning length = ${this.isFromRunning[from] ? this.isFromRunning[from].length : 'none'}`, + ); + if (err) { + this.log.warn(`Cannot get "from" for "${from}": ${err}`); + this.isFromRunning[from]?.forEach(f => + f.cb?.(new Error(`Cannot get "from" for "${from}": ${err}`)), + ); + } else { + this.isFromRunning[from]?.forEach(f => f.cb?.()); + } + this.isFromRunning[from] = null; + }); + } + return; + } + + if (state.ts) { + state.ts = parseInt(state.ts as unknown as string, 10); + } + + try { + if (state.val !== null && typeof state.val === 'object') { + state.val = JSON.stringify(state.val); + } + } catch { + const error = `Cannot convert the object value "${id}"`; + this.log.error(error); + cb?.(new Error(error)); + return; + } + + cb?.(); + } + + prepareTaskCheckTypeAndDbId( + id: string, + state: IobDataEntryEx, + isCounter: boolean, + cb?: (err?: Error | null) => void, + ): void { + // check if we know about this ID + if (!this.sqlDPs[id]) { + if (cb) { + setImmediate(() => cb(new Error(`Unknown ID: ${id}`))); + } + return; + } + + // Check SQL connection + if (!this.clientPool) { + this.log.warn('No Connection to database'); + if (cb) { + setImmediate(() => cb(new Error('No Connection to database'))); + } + return; + } + this.log.debug(`prepareTaskCheckTypeAndDbId CALLED for ${id}`); + + // read type of value + if (this.sqlDPs[id].type !== undefined) { + this.prepareTaskReadDbId(id, state, isCounter, cb); + } else { + // read type from DB + this.tasksReadType.push({ + id, + state, + cb: err => { + if (err) { + cb?.(err); + } else { + this.prepareTaskReadDbId(id, state, isCounter, cb); + } + }, + }); + + if (this.tasksReadType.length === 1) { + this.processReadTypes(); + } + } + } + + pushValueIntoDB( + id: string, + state: IobDataEntryEx, + isCounter: boolean, + storeInCacheOnly?: boolean, + cb?: (err?: Error | null) => void, + ): void { + if (!this.sqlDPs[id] || !state) { + return cb?.(); + } + + this.log.debug( + `pushValueIntoDB called for ${id} (type: ${this.sqlDPs[id].type}, ID: ${this.sqlDPs[id].index}) and state: ${JSON.stringify(state)}`, + ); + + this.prepareTaskCheckTypeAndDbId(id, state, isCounter, (err?: Error | null): void => { + if (!this.sqlDPs[id]) { + return cb?.(); + } + this.log.debug( + `pushValueIntoDB-prepareTaskCheckTypeAndDbId RESULT for ${id} (type: ${this.sqlDPs[id].type}, ID: ${this.sqlDPs[id].index}) and state: ${JSON.stringify(state)}: ${err}`, + ); + if (err) { + return cb?.(err); + } + + const type = this.sqlDPs[id].type; + + // increase timestamp if last is the same + if (!isCounter && this.sqlDPs[id].ts && state.ts === this.sqlDPs[id].ts) { + state.ts++; + } + + // remember last timestamp + this.sqlDPs[id].ts = state.ts; + + // if it was not deleted in this time + this.sqlDPs[id].list ||= []; + + this.sqlDPs[id].list.push({ + state, + from: state.from ? this.from[state.from] : 0, + table: isCounter ? 'ts_counter' : dbNames[type], + }); + + const _settings = this.sqlDPs[id].config || {}; + const maxLength = _settings.maxLength !== undefined ? _settings.maxLength : this.config.maxLength || 0; + if ((cb || (_settings && this.sqlDPs[id].list.length > maxLength)) && !storeInCacheOnly) { + this.storeCached(id, cb); + } else if (cb && storeInCacheOnly) { + setImmediate(cb); + } + }); + } + + storeCached(onlyId?: string, cb?: ((err?: Error | null) => void) | null): void { + let count = 0; + for (const id in this.sqlDPs) { + if (!Object.prototype.hasOwnProperty.call(this.sqlDPs, id) || (onlyId !== undefined && onlyId !== id)) { + continue; + } + + const _settings = this.sqlDPs[id].config || {}; + if (_settings && this.sqlDPs[id]?.list?.length) { + if (_settings.enableDebugLogs) { + this.log.debug(`inserting ${this.sqlDPs[id].list.length} entries from ${id} to DB`); + } + const inFlightId = `${id}_${Date.now()}_${Math.random()}`; + this.sqlDPs[id].inFlight ||= {}; + this.sqlDPs[id].inFlight[inFlightId] = this.sqlDPs[id].list; + this.sqlDPs[id].list = []; + count++; + this.pushValuesIntoDB(id, this.sqlDPs[id].inFlight[inFlightId], (err?: Error | null): void => { + if (this.sqlDPs[id]?.inFlight?.[inFlightId]) { + delete this.sqlDPs[id].inFlight[inFlightId]; + } + if (!--count && cb) { + cb?.(err); + cb = null; + } + }); + if (onlyId !== undefined) { + break; + } + } + } + if (!count && cb) { + cb(); + } + } + + pushValuesIntoDB( + id: string, + list: { state: IobDataEntryEx; from: number; table: TableName }[], + cb?: (err?: Error | null) => void, + ): void { + if (!list.length) { + if (cb) { + setImmediate(() => cb()); + } + return; + } + + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + cb?.(new Error(error)); + } else { + this.tasks.push({ operation: 'insert', index: this.sqlDPs[id].index, list, id, callback: cb }); + this.tasks.length === 1 && this.processTasks(); + } + } else { + const query = this.sqlFuncs!.insert(this.config.dbname, this.sqlDPs[id].index, list); + this._insertValueIntoDB(query, id, cb); + } + } + + processTasks(): void { + if (this.lockTasks) { + return this.log.debug('Tries to execute task, but last one not finished!'); + } + + this.lockTasks = true; + + if (this.tasks.length) { + if (this.tasks[0].operation === 'query') { + const taskQuery: TaskQuery = this.tasks[0]; + this._executeQuery(taskQuery.query, this.tasks[0].id, () => { + taskQuery.callback?.(); + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } else if (this.tasks[0].operation === 'insert') { + const taskInsert: TaskInsert = this.tasks[0]; + const callbacks: ((err?: Error | null) => void)[] = []; + if (taskInsert.callback) { + callbacks.push(taskInsert.callback); + } + for (let i = 1; i < this.tasks.length; i++) { + if (this.tasks[i].operation === 'insert') { + const _taskInsert: TaskInsert = this.tasks[i] as TaskInsert; + if (taskInsert.index === _taskInsert.index) { + taskInsert.list = taskInsert.list.concat(_taskInsert.list); + if (_taskInsert.callback) { + callbacks.push(_taskInsert.callback); + } + this.tasks.splice(i, 1); + i--; + } + } + } + const query = this.sqlFuncs!.insert(this.config.dbname, this.tasks[0].index, this.tasks[0].list); + this._insertValueIntoDB(query, this.tasks[0].id, () => { + callbacks.forEach(cb => cb()); + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } else if (this.tasks[0].operation === 'select') { + const taskSelect: TaskSelect = this.tasks[0]; + this.#getDataFromDB(taskSelect.query, taskSelect.options, (err, rows) => { + taskSelect.callback?.(err, rows); + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } else if (this.tasks[0].operation === 'userQuery') { + const taskUserQuery: TaskUserQuery = this.tasks[0]; + this._userQuery(taskUserQuery.msg, () => { + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } else if (this.tasks[0].operation === 'delete') { + const taskDelete: TaskDelete = this.tasks[0]; + this._checkRetention(taskDelete.query, () => { + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + }); + } else { + this.log.error(`unknown task: ${(this.tasks[0] as any).operation}`); + (this.tasks[0] as any).callback?.('Unknown task'); + this.tasks.shift(); + this.lockTasks = false; + if (this.tasks.length) { + setTimeout(() => this.processTasks(), this.config.requestInterval); + } + } + } + } + + // may be it is required to cache all the data in memory + getId(id: string, type: 0 | 1 | 2 | null, cb: (err: Error | null, id: string) => void): void { + let query = this.sqlFuncs!.getIdSelect(this.config.dbname, id); + this.log.debug(query); + + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + cb?.(err || new Error('No client'), id); + return; + } + + client.execute<{ id: number; name: string; type: 0 | 1 | 2 }>(query, (err, rows /* , fields */) => { + if (!this.sqlDPs[id]) { + this.returnClientToPool(client); + cb?.(new Error(`ID ${id} no longer active`), id); + return; + } + + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot select ${query}: ${err}`); + cb?.(err, id); + return; + } + if (!rows?.length) { + if (type !== null && type !== undefined) { + // insert + query = this.sqlFuncs!.getIdInsert(this.config.dbname, id, type); + + this.log.debug(query); + + client.execute(query, (err /* , rows, fields */) => { + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot insert ${query}: ${err}`); + cb?.(err, id); + } else { + query = this.sqlFuncs!.getIdSelect(this.config.dbname, id); + + this.log.debug(query); + + client.execute<{ id: number; name: string; type: 0 | 1 | 2 }>( + query, + (err, rows /* , fields */) => { + this.returnClientToPool(client); + + if (err) { + this.log.error(`Cannot select ${query}: ${err}`); + cb?.(err, id); + } else if (rows?.[0]) { + this.sqlDPs[id].index = rows[0].id; + this.sqlDPs[id].type = rows[0].type; + + cb?.(null, id); + } else { + this.log.error(`No result for select ${query}: after insert`); + cb?.(new Error(`No result for select ${query}: after insert`), id); + } + }, + ); + } + }); + } else { + this.returnClientToPool(client); + cb?.(new Error('id not found'), id); + } + } else { + this.sqlDPs[id].index = rows[0].id; + if (rows[0].type === null || typeof rows[0].type !== 'number') { + this.sqlDPs[id].type = type === null ? 1 : type; // default string + + const query = this.sqlFuncs!.getIdUpdate( + this.config.dbname, + this.sqlDPs[id].index, + this.sqlDPs[id].type, + ); + + this.log.debug(query); + + client.execute(query, err => { + this.returnClientToPool(client); + if (err) { + this.log.error( + `error updating history config for ${id} to pin datatype: ${query}: ${err}`, + ); + } else { + this.log.info(`changed history configuration to pin detected datatype for ${id}`); + } + cb?.(null, id); + }); + } else { + this.returnClientToPool(client); + + this.sqlDPs[id].type = rows[0].type; + + cb?.(null, id); + } + } + }); + }); + } + + // may be it is required to cache all the data in memory + getFrom(_from: string, cb: (err: Error | null | undefined, from: string) => void): void { + // const sources = (this.config.dbtype !== 'postgresql' ? (this.config.dbname + '.') : '') + 'sources'; + let query = this.sqlFuncs!.getFromSelect(this.config.dbname, _from); + this.log.debug(query); + + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + return cb?.(err || new Error('No client'), _from); + } + client.execute<{ id: number }>(query, (err, rows /* , fields */) => { + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot select ${query}: ${err}`); + return cb?.(err, _from); + } + if (!rows?.length) { + // insert + query = this.sqlFuncs!.getFromInsert(this.config.dbname, _from); + this.log.debug(query); + client.execute(query, err => { + if (err) { + this.returnClientToPool(client); + this.log.error(`Cannot insert ${query}: ${err}`); + return cb?.(err, _from); + } + + query = this.sqlFuncs!.getFromSelect(this.config.dbname, _from); + this.log.debug(query); + client.execute<{ id: number }>(query, (err, rows /* , fields */) => { + this.returnClientToPool(client); + if (err || !rows?.length) { + this.log.error(`Cannot select ${query}: ${err}`); + return cb?.(err, _from); + } + this.from[_from] = rows[0].id; + + cb?.(null, _from); + }); + }); + } else { + this.returnClientToPool(client); + + this.from[_from] = rows[0].id; + + cb?.(null, _from); + } + }); + }); + } + + getOneCachedData( + id: string, + options: ioBroker.GetHistoryOptions & { id: string | null }, + cache: IobDataEntryEx[], + ): boolean { + if (this.sqlDPs[id]) { + let anyInflight = false; + let res: { state: IobDataEntryEx; from: number; table: TableName }[] = []; + for (const inFlightId in this.sqlDPs[id].inFlight) { + this.log.debug( + `getOneCachedData: add ${this.sqlDPs[id].inFlight[inFlightId].length} inFlight datapoints for ${options.id}`, + ); + res = res.concat(this.sqlDPs[id].inFlight[inFlightId]); + anyInflight ||= !!this.sqlDPs[id].inFlight[inFlightId].length; + } + res = res.concat(this.sqlDPs[id].list); + // todo can be optimized + if (res) { + let iProblemCount = 0; + let vLast = null; + for (let i = res.length - 1; i >= 0; i--) { + if (!res[i] || !res[i].state) { + iProblemCount++; + continue; + } + if (options.start && res[i].state.ts < options.start) { + // add one before start + cache.unshift({ ...res[i].state }); + break; + } else if (res[i].state.ts > options.end!) { + // add one after end + vLast = res[i].state; + continue; + } + + if (vLast) { + cache.unshift({ ...vLast }); + vLast = null; + } + + cache.unshift({ ...res[i].state }); + + if ( + options.returnNewestEntries && + options.count && + cache.length >= options.count && + (options.aggregate === 'onchange' || !options.aggregate || options.aggregate === 'none') + ) { + break; + } + } + + if (iProblemCount) { + this.log.warn(`getOneCachedData: got null states ${iProblemCount} times for ${options.id}`); + } + + this.log.debug(`getOneCachedData: got ${res.length} datapoints for ${options.id}`); + return anyInflight; + } + this.log.debug(`getOneCachedData: datapoints for ${options.id} do not yet exist`); + } + return false; + } + + getCachedData( + options: ioBroker.GetHistoryOptions & { id: string | null; index: number | null }, + callback: ( + cache: (IobDataEntryEx & { date?: Date })[], + isFull: boolean, + includesInFlightData: boolean, + earliestTs: number | null, + ) => void, + ): void { + const cache: (IobDataEntryEx & { date?: Date })[] = []; + let anyInflight = false; + + if (options.id) { + anyInflight = this.getOneCachedData(options.id, options, cache); + } else { + for (const id in this.sqlDPs) { + if (Object.prototype.hasOwnProperty.call(this.sqlDPs, id)) { + anyInflight ||= this.getOneCachedData(id, options, cache); + } + } + } + + let earliestTs: number | null = null; + for (let c = 0; c < cache.length; c++) { + if (typeof cache[c].ts === 'string') { + cache[c].ts = parseInt(cache[c].ts as unknown as string, 10); + } + + if (this.common?.loglevel === 'debug') { + cache[c].date = new Date(cache[c].ts); + } + if (options.ack) { + cache[c].ack = !!cache[c].ack; + } + if (typeof cache[c].val === 'number' && isFinite(cache[c].val as any) && options.round) { + cache[c].val = Math.round((cache[c].val as number) * options.round) / options.round; + } + if (options.id && this.sqlDPs[options.id] && this.sqlDPs[options.id].type === 2) { + // 2 === boolean + cache[c].val = !!cache[c].val; + } + if (options.addId && !cache[c].id && options.id) { + cache[c].id = options.id; + } + if (earliestTs === null || cache[c].ts < earliestTs) { + earliestTs = cache[c].ts; + } + } + + // options.length = cache.length; + callback( + cache, + !!options.returnNewestEntries && !!options.count && cache.length >= options.count, + anyInflight, + earliestTs, + ); + } + + #getDataFromDB( + query: string, + options: ioBroker.GetHistoryOptions & { id: string | null; index: number | null }, + callback?: (err: Error | null, result?: (IobDataEntryEx & { date?: Date; id?: string })[]) => void, + ): void { + this.log.debug(query); + + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + callback?.(err || new Error('No client')); + return; + } + client.execute(query, (err, rows /* , fields */): void => { + this.returnClientToPool(client); + + if (!err && rows) { + for (let c = 0; c < rows.length; c++) { + if (typeof rows[c].ts === 'string') { + rows[c].ts = parseInt(rows[c].ts as unknown as string, 10); + } + + if (this.common?.loglevel === 'debug') { + rows[c].date = new Date(rows[c].ts); + } + if (options.ack) { + rows[c].ack = !!rows[c].ack; + } + if (typeof rows[c].val === 'number' && isFinite(rows[c].val as any) && options.round) { + rows[c].val = Math.round((rows[c].val as number) * options.round) / options.round; + } + if (options.id && this.sqlDPs[options.id] && this.sqlDPs[options.id].type === 2) { + // 2 === boolean + rows[c].val = !!rows[c].val; + } + if (options.addId && !rows[c].id && options.id) { + rows[c].id = options.id; + } + } + } + callback?.(err, rows); + }); + }); + } + + getDataFromDB( + table: TableName, + options: ioBroker.GetHistoryOptions & { id: string | null; index: number | null }, + callback: (err: Error | null, result?: (IobDataEntryEx & { date?: Date; id?: string })[]) => void, + ): void { + const query = this.sqlFuncs!.getHistory(this.config.dbname, table, options); + this.log.debug(query); + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + this.log.error(`Cannot queue new requests, because more than ${MAX_TASKS}`); + callback?.(new Error(`Cannot queue new requests, because more than ${MAX_TASKS}`)); + } else { + this.tasks.push({ operation: 'select', query, options, callback }); + if (this.tasks.length === 1) { + this.processTasks(); + } + } + } else { + this.#getDataFromDB(query, options, callback); + } + } + + getCounterDataFromDB( + options: { + id: string; + index: number; + start: number; + end: number; + }, + callback?: (err: Error | null, result?: (IobDataEntryEx & { date?: Date; id?: string })[]) => void, + ): void { + const query = this.sqlFuncs!.getCounterDiff(this.config.dbname, options); + + this.log.debug(query); + + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + callback?.(new Error(error)); + } else { + this.tasks.push({ operation: 'select', query, options, callback }); + if (this.tasks.length === 1) { + this.processTasks(); + } + } + } else { + this.#getDataFromDB(query, options, callback); + } + } + + getCounterDiff(msg: ioBroker.Message): void { + const id: string = msg.message.id; + const start: number = msg.message.options.start || 0; + const end: number = msg.message.options.end || Date.now() + 5000000; + + if (!this.sqlDPs[id]) { + this.sendTo(msg.from, msg.command, { result: [], step: null, error: 'Not enabled' }, msg.callback); + } else { + if (!this.sqlFuncs!.getCounterDiff) { + this.sendTo( + msg.from, + msg.command, + { result: [], step: null, error: 'Counter option is not enabled for this type of SQL' }, + msg.callback, + ); + } else { + const options = { id, start, end, index: this.sqlDPs[id].index }; + this.getCounterDataFromDB(options, (err, data) => + sendResponseCounter( + this as unknown as ioBroker.Adapter, + msg, + options, + err?.toString() || (data as IobDataEntry[]) || [], + ), + ); + } + } + } + + getHistorySql(msg: ioBroker.Message): void { + const startTime = Date.now(); + + if (!msg.message?.options) { + return this.sendTo( + msg.from, + msg.command, + { + error: 'Invalid call. No options for getHistory provided', + }, + msg.callback, + ); + } + let ignoreNull: ioBroker.GetHistoryOptions['ignoreNull']; + if (msg.message.options.ignoreNull === 'true') { + ignoreNull = true; + } // include nulls and replace them with last value + if (msg.message.options.ignoreNull === 'false') { + ignoreNull = false; + } // include nulls + if (msg.message.options.ignoreNull === '0') { + ignoreNull = 0; + } // include nulls and replace them with 0 + if ( + msg.message.options.ignoreNull !== true && + msg.message.options.ignoreNull !== false && + msg.message.options.ignoreNull !== 0 + ) { + ignoreNull = false; + } + const logId: string = (msg.message.id ? msg.message.id : 'all') + Date.now() + Math.random(); + const options: ioBroker.GetHistoryOptions & { id: string | null; index: number | null } = { + id: msg.message.id === '*' ? null : msg.message.id, + start: msg.message.options.start, + end: msg.message.options.end || Date.now() + 5000000, + step: parseInt(msg.message.options.step, 10) || undefined, + count: parseInt(msg.message.options.count, 10), + ignoreNull, + aggregate: msg.message.options.aggregate || 'average', // One of: max, min, average, total, none, on-change + limit: parseInt(msg.message.options.limit, 10) || parseInt(msg.message.options.count, 10) || 2000, + from: msg.message.options.from || false, + q: msg.message.options.q || false, + ack: msg.message.options.ack || false, + addId: msg.message.options.addId || false, + sessionId: msg.message.options.sessionId, + returnNewestEntries: msg.message.options.returnNewestEntries || false, + percentile: + msg.message.options.aggregate === 'percentile' + ? parseInt(msg.message.options.percentile, 10) || 50 + : undefined, + quantile: + msg.message.options.aggregate === 'quantile' + ? parseFloat(msg.message.options.quantile) || 0.5 + : undefined, + integralUnit: + msg.message.options.aggregate === 'integral' + ? parseInt(msg.message.options.integralUnit, 10) || 60 + : undefined, + integralInterpolation: + msg.message.options.aggregate === 'integral' + ? msg.message.options.integralInterpolation || 'none' + : null, + removeBorderValues: msg.message.options.removeBorderValues || false, + // ID in database + index: msg.message.id === '*' ? null : this.sqlDPs[msg.message.id]?.index || null, + }; + + this.log.debug(`${logId} getHistory message: ${JSON.stringify(msg.message)}`); + + if (!options.count || isNaN(options.count)) { + if (options.aggregate === 'none' || options.aggregate === 'onchange') { + options.count = options.limit; + } else { + options.count = 500; + } + } + + try { + if (options.start && typeof options.start !== 'number') { + options.start = new Date(options.start).getTime(); + } + } catch { + return this.sendTo( + msg.from, + msg.command, + { + error: `Invalid call. Start date ${JSON.stringify(options.start)} is not a valid date`, + }, + msg.callback, + ); + } + + try { + if (options.end && typeof options.end !== 'number') { + options.end = new Date(options.end).getTime(); + } + } catch { + return this.sendTo( + msg.from, + msg.command, + { + error: `Invalid call. End date ${JSON.stringify(options.end)} is not a valid date`, + }, + msg.callback, + ); + } + + if (!options.start && options.count) { + options.returnNewestEntries = true; + } + + if ( + msg.message.options.round !== null && + msg.message.options.round !== undefined && + msg.message.options.round !== '' + ) { + msg.message.options.round = parseInt(msg.message.options.round, 10); + if (!isFinite(msg.message.options.round) || msg.message.options.round < 0) { + options.round = this.config.round === null ? undefined : this.config.round; + } else { + options.round = Math.pow(10, parseInt(msg.message.options.round, 10)); + } + } else { + options.round = this.config.round === null ? undefined : this.config.round; + } + + if (options.id && this.aliasMap[options.id]) { + options.id = this.aliasMap[options.id]; + } + + if ((options.aggregate === 'percentile' && options.percentile! < 0) || options.percentile! > 100) { + this.log.error(`Invalid percentile value: ${options.percentile}, use 50 as default`); + options.percentile = 50; + } + + if ((options.aggregate === 'quantile' && options.quantile! < 0) || options.quantile! > 1) { + this.log.error(`Invalid quantile value: ${options.quantile}, use 0.5 as default`); + options.quantile = 0.5; + } + + if ( + options.aggregate === 'integral' && + (typeof options.integralUnit !== 'number' || options.integralUnit <= 0) + ) { + this.log.error(`Invalid integralUnit value: ${options.integralUnit}, use 60s as default`); + options.integralUnit = 60; + } + + if (options.id) { + this.sqlDPs[options.id] ||= {} as SQLPointConfig; + } + const debugLog = !!( + (options.id && this.sqlDPs[options.id]?.config?.enableDebugLogs) || + this.config.enableDebugLogs + ); + + if (options.start! > options.end!) { + const _end = options.end; + options.end = options.start; + options.start = _end; + } + + if (!options.start && !options.count) { + options.start = Date.now() - 86400000; // - 1 day + } + + if (debugLog) { + this.log.debug(`${logId} getHistory options final: ${JSON.stringify(options)}`); + } + + if (options.id && this.sqlDPs[options.id].type === undefined && this.sqlDPs[options.id].dbType !== undefined) { + const storageType = this.sqlDPs[options.id].config?.storageType; + if (storageType) { + if (storageTypes.indexOf(storageType) === this.sqlDPs[options.id].dbType) { + if (debugLog) { + this.log.debug( + `${logId} For getHistory for id ${options.id}: Type empty, use storageType dbType ${this.sqlDPs[options.id].dbType}`, + ); + } + this.sqlDPs[options.id].type = this.sqlDPs[options.id].dbType; + } + } else { + if (debugLog) { + this.log.debug( + `${logId} For getHistory for id ${options.id}: Type empty, use dbType ${this.sqlDPs[options.id].dbType}`, + ); + } + this.sqlDPs[options.id].type = this.sqlDPs[options.id].dbType; + } + } + if (options.id && this.sqlDPs[options.id].index === undefined) { + // read or create in DB + return this.getId(options.id, null, err => { + if (err) { + this.log.warn(`Cannot get index of "${options.id}": ${err}`); + sendResponse(this as unknown as ioBroker.Adapter, msg, options.id!, options, [], startTime); + } else { + this.getHistorySql(msg); + } + }); + } + if (options.id && this.sqlDPs[options.id].type === undefined) { + this.log.warn( + `For getHistory for id "${options.id}": Type empty. Need to write data first. Index = ${this.sqlDPs[options.id].index}`, + ); + sendResponse( + this as unknown as ioBroker.Adapter, + msg, + options.id, + options, + 'Please wait till next data record is logged and reload.', + startTime, + ); + return; + } + + // if specific id requested + if (options.id) { + const type = this.sqlDPs[options.id].type; + options.index = this.sqlDPs[options.id].index; + + this.getCachedData(options, (cacheData, isFull, includesInFlightData, earliestTs) => { + if (debugLog) { + this.log.debug(`${logId} after getCachedData: length = ${cacheData.length}, isFull=${isFull}`); + } + + // if all data read + if ( + isFull && + cacheData.length && + (options.aggregate === 'onchange' || !options.aggregate || options.aggregate === 'none') + ) { + cacheData.sort(sortByTs); + if (options.count && cacheData.length > options.count && options.aggregate === 'none') { + cacheData.splice(0, cacheData.length - options.count); + if (debugLog) { + this.log.debug(`${logId} cut cacheData to ${options.count} values`); + } + } + this.log.debug(`${logId} Send: ${cacheData.length} values in: ${Date.now() - startTime}ms`); + + this.sendTo( + msg.from, + msg.command, + { + result: cacheData, + step: null, + error: null, + }, + msg.callback, + ); + } else { + const origEnd = options.end; + if (includesInFlightData && earliestTs) { + options.end = earliestTs; + } + // if not all data read + this.getDataFromDB(dbNames[type], options, (err, data) => { + if (!err && data) { + options.end = origEnd; + if (options.aggregate === 'none' && options.count && options.returnNewestEntries) { + cacheData = cacheData.reverse(); + data = cacheData.concat(data); + } else { + data = data.concat(cacheData); + } + if (debugLog) { + this.log.debug(`${logId} after getDataFromDB: length = ${data.length}`); + } + if ( + options.count && + data.length > options.count && + options.aggregate === 'none' && + !options.returnNewestEntries + ) { + if (options.start) { + for (let i = 0; i < data.length; i++) { + if (data[i].ts < options.start) { + data.splice(i, 1); + i--; + } else { + break; + } + } + } + data.splice(options.count); + if (debugLog) { + this.log.debug(`${logId} pre-cut data to ${options.count} oldest values`); + } + } + + data.sort(sortByTs); + } + try { + sendResponse( + this as unknown as ioBroker.Adapter, + msg, + options.id!, + options, + err?.toString() || (data as IobDataEntry[]) || [], + startTime, + debugLog ? logId : undefined, + ); + } catch (e) { + sendResponse( + this as unknown as ioBroker.Adapter, + msg, + options.id!, + options, + e.toString(), + startTime, + ); + } + }); + } + }); + } else { + // if all IDs requested + let rows: (IobDataEntryEx & { date?: Date; id?: string })[] = []; + let count = 0; + this.getCachedData(options, (cacheData, isFull, includesInFlightData, earliestTs) => { + if (isFull && cacheData.length) { + cacheData.sort(sortByTs); + sendResponse( + this as unknown as ioBroker.Adapter, + msg, + '', + options, + cacheData as IobDataEntry[], + startTime, + debugLog ? logId : undefined, + ); + } else { + if (includesInFlightData && earliestTs) { + options.end = earliestTs; + } + for (let db = 0; db < dbNames.length; db++) { + count++; + this.getDataFromDB(dbNames[db], options, (err, data) => { + if (data) { + rows = rows.concat(data); + } + if (!--count) { + rows.sort(sortByTs); + try { + sendResponse( + this as unknown as ioBroker.Adapter, + msg, + '', + options, + rows as IobDataEntry[], + startTime, + ); + } catch (e) { + sendResponse( + this as unknown as ioBroker.Adapter, + msg, + '', + options, + e.toString(), + startTime, + ); + } + } + }); + } + } + }); + } + } + + update(id: string, state: ioBroker.State, cb?: (err?: Error | null) => void): void { + // first try to find the value in not yet saved data + let found = false; + if (this.sqlDPs[id]) { + const res = this.sqlDPs[id].list; + if (res) { + for (let i = res.length - 1; i >= 0; i--) { + if (res[i].state.ts === state.ts) { + if (state.val !== undefined) { + res[i].state.val = state.val; + } + if (state.q !== undefined && res[i].state.q !== undefined) { + res[i].state.q = state.q; + } + if (state.from !== undefined && res[i].from !== undefined) { + res[i].state.from = state.from; + } + if (state.ack !== undefined) { + res[i].state.ack = state.ack; + } + found = true; + break; + } + } + } + } + + if (!found) { + this.prepareTaskCheckTypeAndDbId(id, state, false, (err?: Error | null): void => { + if (err) { + return cb?.(err); + } + + const type = this.sqlDPs[id].type; + + const query = this.sqlFuncs!.update( + this.config.dbname, + this.sqlDPs[id].index, + state, + this.from[state.from], + dbNames[type], + ); + + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + cb?.(new Error(error)); + } else { + this.tasks.push({ operation: 'query', query, id, callback: cb }); + this.tasks.length === 1 && this.processTasks(); + } + } else { + this._executeQuery(query, id, cb); + } + }); + } else { + cb?.(); + } + } + + #delete( + id: string, + state: { + ts?: number; + start?: number; + end?: number; + }, + cb?: (err?: Error | null) => void, + ): void { + // first try to find the value in not yet saved data + let found = false; + if (this.sqlDPs[id]) { + const res = this.sqlDPs[id].list; + if (res) { + if (!state.ts && !state.start && !state.end) { + this.sqlDPs[id].list = []; + } else { + for (let i = res.length - 1; i >= 0; i--) { + if (state.start && state.end) { + if (res[i].state.ts >= state.start && res[i].state.ts <= state.end) { + res.splice(i, 1); + } + } else if (state.start) { + if (res[i].state.ts >= state.start) { + res.splice(i, 1); + } + } else if (state.end) { + if (res[i].state.ts <= state.end) { + res.splice(i, 1); + } + } else if (res[i].state.ts === state.ts) { + res.splice(i, 1); + found = true; + break; + } + } + } + } + } + + if (!found) { + this.prepareTaskCheckTypeAndDbId(id, state as unknown as IobDataEntryEx, false, err => { + if (err) { + return cb?.(err); + } + + const type = this.sqlDPs[id].type; + + let query; + if (state.start && state.end) { + query = this.sqlFuncs!.deleteFromTable( + this.config.dbname, + dbNames[type], + this.sqlDPs[id].index, + state.start, + state.end, + ); + } else if (state.ts) { + query = this.sqlFuncs!.deleteFromTable( + this.config.dbname, + dbNames[type], + this.sqlDPs[id].index, + state.ts, + ); + } else { + // delete all entries for ID + query = this.sqlFuncs!.deleteFromTable(this.config.dbname, dbNames[type], this.sqlDPs[id].index); + } + + if (!this.multiRequests) { + if (this.tasks.length > MAX_TASKS) { + const error = `Cannot queue new requests, because more than ${MAX_TASKS}`; + this.log.error(error); + cb?.(new Error(error)); + } else { + this.tasks.push({ operation: 'query', query, id, callback: cb }); + this.tasks.length === 1 && this.processTasks(); + } + } else { + this._executeQuery(query, id, cb); + } + }); + } else { + cb?.(); + } + } + + updateState(msg: ioBroker.Message): void { + if (!msg.message) { + this.log.error('updateState called with invalid data'); + return this.sendTo( + msg.from, + msg.command, + { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, + msg.callback, + ); + } + let id; + if (Array.isArray(msg.message)) { + this.log.debug(`updateState ${msg.message.length} items`); + for (let i = 0; i < msg.message.length; i++) { + id = this.aliasMap[msg.message[i].id] ? this.aliasMap[msg.message[i].id] : msg.message[i].id; + + if (msg.message[i].state && typeof msg.message[i].state === 'object') { + this.update(id, msg.message[i].state); + } else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message[i])}`); + } + } + } else if (msg.message.state && Array.isArray(msg.message.state)) { + this.log.debug(`updateState ${msg.message.state.length} items`); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + for (let j = 0; j < msg.message.state.length; j++) { + if (msg.message.state[j] && typeof msg.message.state[j] === 'object') { + this.update(id, msg.message.state[j]); + } else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message.state[j])}`); + } + } + } else if (msg.message.id && msg.message.state && typeof msg.message.state === 'object') { + this.log.debug('updateState 1 item'); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + return this.update(id, msg.message.state, () => + this.sendTo( + msg.from, + msg.command, + { + success: true, + sqlConnected: !!this.clientPool, + }, + msg.callback, + ), + ); + } else { + this.log.error('updateState called with invalid data'); + return this.sendTo( + msg.from, + msg.command, + { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, + msg.callback, + ); + } + + this.sendTo( + msg.from, + msg.command, + { + success: true, + sqlConnected: !!this.clientPool, + }, + msg.callback, + ); + } + + deleteHistoryEntry(msg: ioBroker.Message): void { + if (!msg.message) { + this.log.error('deleteHistoryEntry called with invalid data'); + return this.sendTo( + msg.from, + msg.command, + { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, + msg.callback, + ); + } + let id; + if (Array.isArray(msg.message)) { + this.log.debug(`deleteHistoryEntry ${msg.message.length} items`); + for (let i = 0; i < msg.message.length; i++) { + id = this.aliasMap[msg.message[i].id] ? this.aliasMap[msg.message[i].id] : msg.message[i].id; + + // {id: 'blabla', ts: 892} + if (msg.message[i].ts) { + this.#delete(id, { ts: msg.message[i].ts }); + } else if (msg.message[i].start) { + if (typeof msg.message[i].start === 'string') { + msg.message[i].start = new Date(msg.message[i].start).getTime(); + } + if (typeof msg.message[i].end === 'string') { + msg.message[i].end = new Date(msg.message[i].end).getTime(); + } + this.#delete(id, { start: msg.message[i].start, end: msg.message[i].end || Date.now() }); + } else if ( + typeof msg.message[i].state === 'object' && + msg.message[i].state && + msg.message[i].state.ts + ) { + this.#delete(id, { ts: msg.message[i].state.ts }); + } else if ( + typeof msg.message[i].state === 'object' && + msg.message[i].state && + msg.message[i].state.start + ) { + if (typeof msg.message[i].state.start === 'string') { + msg.message[i].state.start = new Date(msg.message[i].state.start).getTime(); + } + if (typeof msg.message[i].state.end === 'string') { + msg.message[i].state.end = new Date(msg.message[i].state.end).getTime(); + } + this.#delete(id, { + start: msg.message[i].state.start, + end: msg.message[i].state.end || Date.now(), + }); + } else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message[i])}`); + } + } + } else if (msg.message.state && Array.isArray(msg.message.state)) { + this.log.debug(`deleteHistoryEntry ${msg.message.state.length} items`); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + + for (let j = 0; j < msg.message.state.length; j++) { + if (msg.message.state[j] && typeof msg.message.state[j] === 'object') { + if (msg.message.state[j].ts) { + this.#delete(id, { ts: msg.message.state[j].ts }); + } else if (msg.message.state[j].start) { + if (typeof msg.message.state[j].start === 'string') { + msg.message.state[j].start = new Date(msg.message.state[j].start).getTime(); + } + if (typeof msg.message.state[j].end === 'string') { + msg.message.state[j].end = new Date(msg.message.state[j].end).getTime(); + } + this.#delete(id, { + start: msg.message.state[j].start, + end: msg.message.state[j].end || Date.now(), + }); + } + } else if (msg.message.state[j] && typeof msg.message.state[j] === 'number') { + this.#delete(id, { ts: msg.message.state[j] }); + } else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message.state[j])}`); + } + } + } else if (msg.message.ts && Array.isArray(msg.message.ts)) { + this.log.debug(`deleteHistoryEntry ${msg.message.ts.length} items`); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + for (let j = 0; j < msg.message.ts.length; j++) { + if (msg.message.ts[j] && typeof msg.message.ts[j] === 'number') { + this.#delete(id, { ts: msg.message.ts[j] }); + } else { + this.log.warn(`Invalid state for ${JSON.stringify(msg.message.ts[j])}`); + } + } + } else if (msg.message.id && msg.message.state && typeof msg.message.state === 'object') { + this.log.debug('deleteHistoryEntry 1 item'); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + return this.#delete(id, { ts: msg.message.state.ts }, () => + this.sendTo( + msg.from, + msg.command, + { + success: true, + sqlConnected: !!this.clientPool, + }, + msg.callback, + ), + ); + } else if (msg.message.id && msg.message.ts && typeof msg.message.ts === 'number') { + this.log.debug('deleteHistoryEntry 1 item'); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + return this.#delete(id, { ts: msg.message.ts }, () => + this.sendTo( + msg.from, + msg.command, + { + success: true, + sqlConnected: !!this.clientPool, + }, + msg.callback, + ), + ); + } else { + this.log.error('deleteHistoryEntry called with invalid data'); + return this.sendTo(msg.from, msg.command, { error: `Invalid call: ${JSON.stringify(msg)}` }, msg.callback); + } + + this.sendTo( + msg.from, + msg.command, + { + success: true, + sqlConnected: !!this.clientPool, + }, + msg.callback, + ); + } + + deleteStateAll(msg: ioBroker.Message): void { + if (!msg.message) { + this.log.error('deleteStateAll called with invalid data'); + return this.sendTo( + msg.from, + msg.command, + { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, + msg.callback, + ); + } + let id; + if (Array.isArray(msg.message)) { + this.log.debug(`deleteStateAll ${msg.message.length} items`); + for (let i = 0; i < msg.message.length; i++) { + id = this.aliasMap[msg.message[i].id] ? this.aliasMap[msg.message[i].id] : msg.message[i].id; + this.#delete(id, {}); + } + } else if (msg.message.id) { + this.log.debug('deleteStateAll 1 item'); + id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + return this.#delete(id, {}, () => + this.sendTo( + msg.from, + msg.command, + { + success: true, + sqlConnected: !!this.clientPool, + }, + msg.callback, + ), + ); + } else { + this.log.error('deleteStateAll called with invalid data'); + return this.sendTo(msg.from, msg.command, { error: `Invalid call: ${JSON.stringify(msg)}` }, msg.callback); + } + + this.sendTo( + msg.from, + msg.command, + { + success: true, + sqlConnected: !!this.clientPool, + }, + msg.callback, + ); + } + + storeStatePushData(id: string, state: IobDataEntryEx, applyRules?: boolean): Promise { + if (!state || typeof state !== 'object') { + throw new Error(`State ${JSON.stringify(state)} for ${id} is not valid`); + } + + if (!this.sqlDPs[id]?.config) { + if (applyRules) { + throw new Error(`sql not enabled for ${id}, so can not apply the rules as requested`); + } + this.sqlDPs[id] ||= {} as SQLPointConfig; + this.sqlDPs[id].realId = id; + } + return new Promise((resolve, reject) => { + if (applyRules) { + this.pushHistory(id, state); + resolve(true); + } else { + this.pushHelper(id, state, err => { + if (err) { + reject( + new Error(`Error writing state for ${id}: ${err.message}, Data: ${JSON.stringify(state)}`), + ); + } else { + resolve(true); + } + }); + } + }); + } + + async storeState(msg: ioBroker.Message): Promise { + if (!msg.message) { + this.log.error('storeState called with invalid data'); + return this.sendTo( + msg.from, + msg.command, + { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, + msg.callback, + ); + } + + const errors = []; + let successCount = 0; + if (Array.isArray(msg.message)) { + this.log.debug(`storeState ${msg.message.length} items`); + for (let i = 0; i < msg.message.length; i++) { + const id = this.aliasMap[msg.message[i].id] ? this.aliasMap[msg.message[i].id] : msg.message[i].id; + try { + await this.storeStatePushData(id, msg.message[i].state, msg.message[i].rules); + successCount++; + } catch (err) { + errors.push(err.message); + } + } + } else if (msg.message.id && Array.isArray(msg.message.state)) { + this.log.debug(`storeState ${msg.message.state.length} items`); + const id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + for (let j = 0; j < msg.message.state.length; j++) { + try { + await this.storeStatePushData(id, msg.message.state[j], msg.message.rules); + successCount++; + } catch (err) { + errors.push(err.message); + } + } + } else if (msg.message.id && msg.message.state) { + this.log.debug('storeState 1 item'); + const id = this.aliasMap[msg.message.id] ? this.aliasMap[msg.message.id] : msg.message.id; + try { + await this.storeStatePushData(id, msg.message.state, msg.message.rules); + successCount++; + } catch (err) { + errors.push(err.message); + } + } else { + this.log.error('storeState called with invalid data'); + return this.sendTo( + msg.from, + msg.command, + { + error: `Invalid call: ${JSON.stringify(msg)}`, + }, + msg.callback, + ); + } + if (errors.length) { + this.log.warn(`storeState executed with ${errors.length} errors: ${errors.join(', ')}`); + return this.sendTo( + msg.from, + msg.command, + { + error: `${errors.length} errors happened while storing data`, + errors: errors, + successCount, + }, + msg.callback, + ); + } + + this.log.debug(`storeState executed with ${successCount} states successfully`); + + this.sendTo( + msg.from, + msg.command, + { + success: true, + successCount, + sqlConnected: !!this.clientPool, + }, + msg.callback, + ); + } + + getDpOverview(msg: ioBroker.Message): void { + const result: { [id: string]: { name: string; type?: 'number' | 'string' | 'boolean' } }[] = []; + const query = this.sqlFuncs!.getIdSelect(this.config.dbname); + this.log.info(query); + + this.borrowClientFromPool((err, client) => { + if (err || !client) { + this.returnClientToPool(client); + return this.sendTo( + msg.from, + msg.command, + { + error: `Cannot select ${query}: ${err || 'no client'}`, + }, + msg.callback, + ); + } + client.execute<{ id: number; type: 0 | 1 | 2; name: string }>(query, (err, rows /* , fields */) => { + if (err) { + this.returnClientToPool(client); + + this.log.error(`Cannot select ${query}: ${err}`); + + this.sendTo( + msg.from, + msg.command, + { + error: `Cannot select ${query}: ${err}`, + }, + msg.callback, + ); + return; + } + + this.log.info(`Query result ${JSON.stringify(rows)}`); + + if (rows?.length) { + for (let r = 0; r < rows.length; r++) { + result[rows[r].type] ||= {}; + result[rows[r].type][rows[r].id] = { name: rows[r].name }; + + switch (dbNames[rows[r].type]) { + case 'ts_number': + result[rows[r].type][rows[r].id].type = 'number'; + break; + + case 'ts_string': + result[rows[r].type][rows[r].id].type = 'string'; + break; + + case 'ts_bool': + result[rows[r].type][rows[r].id].type = 'boolean'; + break; + } + } + + this.log.info(`initialisation result: ${JSON.stringify(result)}`); + this.getFirstTsForIds(client, 0, result, msg); + } + }); + }); + } + + getFirstTsForIds( + client: SQLClient, + typeId: 0 | 1 | 2, + resultData: { [id: string]: { name: string; type?: 'number' | 'string' | 'boolean'; ts?: number } }[], + msg: ioBroker.Message, + ): void { + if (typeId < dbNames.length) { + if (!resultData[typeId]) { + this.getFirstTsForIds(client, ((typeId as number) + 1) as 0 | 1 | 2, resultData, msg); + } else { + const query = this.sqlFuncs!.getFirstTs(this.config.dbname, dbNames[typeId]); + this.log.info(query); + + client.execute<{ id: number; ts: number }>(query, (err, rows /* , fields */) => { + if (err) { + this.returnClientToPool(client); + + this.log.error(`Cannot select ${query}: ${err}`); + this.sendTo( + msg.from, + msg.command, + { + error: `Cannot select ${query}: ${err}`, + }, + msg.callback, + ); + return; + } + + this.log.info(`Query result ${JSON.stringify(rows)}`); + + if (rows?.length) { + for (let r = 0; r < rows.length; r++) { + if (resultData[typeId][rows[r].id]) { + resultData[typeId][rows[r].id].ts = rows[r].ts; + } + } + } + + this.log.info(`enhanced result (${typeId}): ${JSON.stringify(resultData)}`); + this.dpOverviewTimeout = setTimeout( + (_client, typeId, resultData, msg) => { + this.dpOverviewTimeout = null; + this.getFirstTsForIds(_client, typeId as 0 | 1 | 2, resultData, msg); + }, + 5000, + client, + typeId + 1, + resultData, + msg, + ); + }); + } + } else { + this.returnClientToPool(client); + + this.log.info('consolidate data ...'); + const result: { [id: string]: { type: 'number' | 'string' | 'boolean' | 'undefined'; ts?: number } } = {}; + for (let ti = 0; ti < dbNames.length; ti++) { + if (resultData[ti]) { + for (const index in resultData[ti]) { + if (!Object.prototype.hasOwnProperty.call(resultData[ti], index)) { + continue; + } + + const id = resultData[ti][index].name; + if (!result[id]) { + result[id] = { + type: resultData[ti][index].type || 'undefined', + ts: resultData[ti][index].ts, + }; + } else { + result[id].type = 'undefined'; + if ( + result[id].ts !== undefined && + (resultData[ti][index].ts === undefined || resultData[ti][index].ts! < result[id].ts) + ) { + result[id].ts = resultData[ti][index].ts; + } + } + } + } + } + this.log.info(`Result: ${JSON.stringify(result)}`); + this.sendTo( + msg.from, + msg.command, + { + success: true, + result: result, + }, + msg.callback, + ); + } + } + + enableHistory(msg: ioBroker.Message): void { + if (!msg.message?.id) { + this.log.error('enableHistory called with invalid data'); + this.sendTo(msg.from, msg.command, { error: 'Invalid call' }, msg.callback); + return; + } + + const obj: ioBroker.StateObject = {} as ioBroker.StateObject; + obj.common = {} as ioBroker.StateCommon; + obj.common.custom = {}; + obj.common.custom[this.namespace] = msg.message.options || {}; + obj.common.custom[this.namespace].enabled = true; + + this.extendForeignObject(msg.message.id, obj, err => { + if (err) { + this.log.error(`enableHistory: ${err}`); + this.sendTo( + msg.from, + msg.command, + { + error: err, + }, + msg.callback, + ); + } else { + this.log.info(JSON.stringify(obj)); + this.sendTo(msg.from, msg.command, { success: true }, msg.callback); + } + }); + } + + disableHistory(msg: ioBroker.Message): void { + if (!msg.message?.id) { + this.log.error('disableHistory called with invalid data'); + return this.sendTo( + msg.from, + msg.command, + { + error: 'Invalid call', + }, + msg.callback, + ); + } + + const obj: ioBroker.StateObject = {} as ioBroker.StateObject; + obj.common = {} as ioBroker.StateCommon; + obj.common.custom = { + [this.namespace]: { + enabled: false, + }, + }; + + this.extendForeignObject(msg.message.id, obj, err => { + if (err) { + this.log.error(`disableHistory: ${err}`); + this.sendTo( + msg.from, + msg.command, + { + error: err, + }, + msg.callback, + ); + } else { + this.log.info(JSON.stringify(obj)); + this.sendTo(msg.from, msg.command, { success: true }, msg.callback); + } + }); + } + + getEnabledDPs(msg: ioBroker.Message): void { + const data: { [id: string]: SqlCustomConfigTyped } = {}; + for (const id in this.sqlDPs) { + if (Object.prototype.hasOwnProperty.call(this.sqlDPs, id) && this.sqlDPs[id]?.config?.enabled) { + data[this.sqlDPs[id].realId] = this.sqlDPs[id].config; + } + } + + this.sendTo(msg.from, msg.command, data, msg.callback); + } + + getDockerConfigMySQL(config: SqlAdapterConfigTyped): ContainerConfig { + config.dockerMysql ||= { + enabled: false, + }; + config.dbtype = 'mysql'; + config.dbname = 'iobroker'; + config.user = 'iobroker'; + config.password = 'iobroker'; + config.dockerMysql.port = parseInt((config.dockerMysql.port as string) || '3306', 10) || 3306; + config.port = config.dockerMysql.port; + config.multiRequests = true; + config.maxConnections = 100; + return { + iobEnabled: true, + iobStopOnUnload: config.dockerMysql.stopIfInstanceStopped || false, + removeOnExit: true, + + // influxdb image: https://hub.docker.com/_/influxdb. Only version 2 is supported + image: 'mysql:lts', + ports: [ + { + hostPort: config.dockerMysql.port, + containerPort: 3306, + hostIP: config.dockerMysql.bind || '127.0.0.1', // only localhost to disable authentication and https safely + }, + ], + mounts: [ + { + source: 'mysql_data', + target: '/var/lib/mysql', + type: 'volume', + iobBackup: true, + }, + { + source: 'mysql_config', + target: '/etc/mysql/conf.d', + type: 'volume', + }, + ], + networkMode: true, // take default name iob_influxdb_ + // influxdb v2 requires some environment variables to be set on first start + environment: { + MYSQL_ROOT_PASSWORD: config.dockerMysql.rootPassword || 'root_iobroker', + MYSQL_DATABASE: 'iobroker', + MYSQL_USER: 'iobroker', + MYSQL_PASSWORD: 'iobroker', + MYSQL_ALLOW_EMPTY_PASSWORD: 'false', + }, + }; + } + + normalizeAdapterConfig(config: SqlAdapterConfig): SqlAdapterConfigTyped { + config.dbname ||= 'iobroker'; + + if (config.writeNulls === undefined) { + config.writeNulls = true; + } + + config.retention = parseInt(config.retention as string, 10) || 0; + if (config.retention === -1) { + // Custom timeframe + config.retention = (parseInt(config.customRetentionDuration as string, 10) || 0) * 24 * 60 * 60; + } + config.debounce = parseInt(config.debounce as string, 10) || 0; + config.requestInterval = + config.requestInterval === undefined || config.requestInterval === null || config.requestInterval === '' + ? 0 + : parseInt(config.requestInterval as string, 10) || 0; + + if (config.changesRelogInterval !== null && config.changesRelogInterval !== undefined) { + config.changesRelogInterval = parseInt(config.changesRelogInterval as string, 10); + } else { + config.changesRelogInterval = 0; + } + + if (!clients[config.dbtype]) { + this.log.error(`Unknown DB type: ${config.dbtype}`); + void this.stop?.(); + } + if (config.multiRequests !== undefined && config.dbtype !== 'sqlite') { + clients[config.dbtype].multiRequests = config.multiRequests; + } + if (config.maxConnections !== undefined && config.dbtype !== 'sqlite') { + config.maxConnections = parseInt(config.maxConnections as string, 10); + if (config.maxConnections !== 0 && !config.maxConnections) { + config.maxConnections = 100; + } + } else { + config.maxConnections = 1; // SQLite does not support multiple connections + } + + if (config.changesMinDelta !== null && config.changesMinDelta !== undefined) { + config.changesMinDelta = parseFloat(config.changesMinDelta.toString().replace(/,/g, '.')); + } else { + config.changesMinDelta = 0; + } + + if (config.blockTime !== null && config.blockTime !== undefined) { + config.blockTime = parseInt(config.blockTime as string, 10) || 0; + } else { + if (config.debounce !== null && config.debounce !== undefined) { + config.debounce = parseInt(config.debounce as unknown as string, 10) || 0; + } else { + config.blockTime = 0; + } + } + + if (config.debounceTime !== null && config.debounceTime !== undefined) { + config.debounceTime = parseInt(config.debounceTime as string, 10) || 0; + } else { + config.debounceTime = 0; + } + + if (config.maxLength !== null && config.maxLength !== undefined) { + config.maxLength = parseInt(config.maxLength as string, 10) || 0; + } else { + config.maxLength = 0; + } + + this.multiRequests = clients[config.dbtype].multiRequests; + if (!this.multiRequests) { + config.writeNulls = false; + } + + config.port = parseInt(config.port as string, 10) || 0; + + if (config.round !== null && config.round !== undefined && config.round !== '') { + config.round = parseInt(config.round as string, 10); + if (!isFinite(config.round) || config.round < 0) { + config.round = null; + this.log.info(`Invalid round value: ${config.round} - ignore, do not round values`); + } else { + config.round = Math.pow(10, config.round); + } + } else { + config.round = null; + } + + return config as SqlAdapterConfigTyped; + } + + async createUserInDocker(): Promise { + const mySQLOptions: MySQLOptions = { + host: this.config.host, // needed for PostgreSQL , MySQL + user: 'root', + password: this.config.dockerMysql.rootPassword || 'root_iobroker', + port: this.config.port || undefined, + ssl: this.config.encrypt + ? { + rejectUnauthorized: !!this.config.rejectUnauthorized, + } + : undefined, + }; + const client = new MySQL2Client(mySQLOptions); + await client.connectAsync(); + // Show all users + const exists = await client.executeAsync<{ ex: 0 | 1 }>( + `SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = 'iobroker') as "ex";`, + ); + if (exists?.[0]?.ex !== 1) { + // create user + await client.executeAsync(`CREATE USER 'iobroker'@'%' IDENTIFIED BY 'iobroker';`); + await client.executeAsync(`GRANT ALL PRIVILEGES ON * . * TO 'iobroker'@'%';`); + await client.executeAsync(`FLUSH PRIVILEGES;`); + } + await client.disconnectAsync(); + } + + async main(): Promise { + this.setConnected(false); + + // set default history if not yet set + try { + const obj = await this.getForeignObjectAsync('system.config'); + if (obj?.common && !obj.common.defaultHistory) { + obj.common.defaultHistory = this.namespace; + await this.setForeignObjectAsync('system.config', obj); + this.log.info(`Set default history instance to "${this.namespace}"`); + } + } catch (e) { + this.log.error(`Cannot get system config: ${e}`); + } + // Normalize adapter config + const config = this.normalizeAdapterConfig(this.config); + + this.sqlFuncs = SQLFuncs[config.dbtype]; + + // Start docker if configured + if (this.config.dockerMysql?.enabled) { + this.config.dbtype = 'mysql'; + this.config.dbname = 'iobroker'; + this.config.user = 'iobroker'; + this.config.password = 'iobroker'; + this.config.dockerMysql.port = parseInt((this.config.dockerMysql.port as string) || '3306', 10) || 3306; + this.config.port = this.config.dockerMysql.port; + this.config.multiRequests = true; + this.config.maxConnections = 100; + + if (this.config.dockerPhpMyAdmin) { + this.config.dockerPhpMyAdmin.port = + parseInt((this.config.dockerPhpMyAdmin.port as string) || '8080', 10) || 8080; + } + + // Check that the user 'iobroker' exists + await this.createUserInDocker(); + } + + if (config.dbtype === 'sqlite' || this.config.host) { + this.connect(() => { + // read all custom settings + this.getObjectView('system', 'custom', {}, (err, doc) => { + let count = 0; + if (doc?.rows) { + for (let i = 0, l = doc.rows.length; i < l; i++) { + if (doc.rows[i].value?.[this.namespace]) { + let id: string = doc.rows[i].id; + const realId = id; + if (doc.rows[i].value[this.namespace] && doc.rows[i].value[this.namespace].aliasId) { + this.aliasMap[id] = doc.rows[i].value[this.namespace].aliasId; + this.log.debug(`Found Alias: ${id} --> ${this.aliasMap[id]}`); + id = this.aliasMap[id]; + } + + let storedIndex: number | null = null; + let storedType: 0 | 1 | 2 | null = null; + if (this.sqlDPs[id] && this.sqlDPs[id].index !== undefined) { + storedIndex = this.sqlDPs[id].index; + } + if (this.sqlDPs[id] && this.sqlDPs[id].dbType !== undefined) { + storedType = this.sqlDPs[id].dbType; + } + const config = this.normalizeCustomConfig(doc.rows[i].value as SqlCustomConfig); + + this.sqlDPs[id] ||= {} as SQLPointConfig; + const sqlDP = this.sqlDPs[id]; + sqlDP.config = config; + if (storedIndex !== null) { + sqlDP.index = storedIndex; + } + if (storedType !== null) { + sqlDP.dbType = storedType; + } + + if (!config || typeof config !== 'object' || config.enabled === false) { + delete this.sqlDPs[id]; + } else { + count++; + this.log.info( + `enabled logging of ${id}, Alias=${id !== realId}, ${count} points now activated`, + ); + + // relogTimeout + if (config.changesOnly && config.changesRelogInterval > 0) { + if (sqlDP.relogTimeout) { + clearTimeout(sqlDP.relogTimeout); + } + sqlDP.relogTimeout = setTimeout( + _id => { + this.sqlDPs[_id].relogTimeout = null; + this.reLogHelper(_id); + }, + sqlDP.config.changesRelogInterval * 500 * (1 + Math.random()), + id, + ); + } + + sqlDP.realId = realId; + sqlDP.list ||= []; + sqlDP.inFlight ||= {}; + + // randomize lastCheck to avoid all datapoints to be checked at the same timepoint + sqlDP.lastCheck = Date.now() - Math.floor(Math.random() * 21600000 /* 6 hours */); + } + } + } + } + + if (this.config.writeNulls) { + this.writeNulls(); + } + + if (count < 200) { + Object.keys(this.sqlDPs).forEach( + id => + this.sqlDPs[id]?.config && + !this.sqlDPs[id].realId && + this.log.warn(`No realID found for ${id}`), + ); + + this.subscribeForeignStates( + Object.keys(this.sqlDPs).filter(id => this.sqlDPs[id]?.config && this.sqlDPs[id].realId), + ); + } else { + this.subscribeAll = true; + this.subscribeForeignStates('*'); + } + this.subscribeForeignObjects('*'); + this.log.debug('Initialization done'); + this.setConnected(true); + this.processStartValues(); + + // store all buffered data every 10 minutes to not lost the data + this.bufferChecker = setInterval(() => this.storeCached(), 10 * 60000); + }); + }); + } + } +} + +// If started as allInOne mode => return function to create instance +if (require.main !== module) { + // Export the constructor in compact mode + module.exports = (options: Partial | undefined) => new SqlAdapter(options); +} else { + // otherwise start the instance directly + (() => new SqlAdapter())(); +} diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 00000000..91e27cf1 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,119 @@ +export type DbType = 'sqlite' | 'postgresql' | 'mysql' | 'mssql'; +export type TableName = 'ts_string' | 'ts_number' | 'ts_bool' | 'ts_counter'; +export type StorageType = '' | 'String' | 'Number' | 'Boolean'; +export interface SqlCustomConfigTyped { + enabled: boolean; + debounceTime: number; + blockTime: number; + changesOnly: boolean; + changesRelogInterval: number; + changesMinDelta: number; + ignoreBelowNumber: number | null; + ignoreAboveNumber: number | null; + ignoreZero: boolean; + disableSkippedValueLogging: boolean; + storageType: StorageType | false; + counter: boolean; + aliasId: string; + retention: number; + customRetentionDuration: number; + maxLength: number; + round: number; + enableDebugLogs: boolean; + debounce: number; + ignoreBelowZero: boolean; +} + +export interface SqlCustomConfig extends SqlCustomConfigTyped { + enabled: boolean | 'true'; + debounceTime: number | string; + changesOnly: boolean | 'true'; + blockTime: number | string; + changesRelogInterval: number | string; + changesMinDelta: number | string; + ignoreZero: boolean | 'true'; + enableDebugLogs: boolean | 'true'; + ignoreBelowNumber: number | string | null; + ignoreAboveNumber: number | string | null; + counter: boolean | 'true'; + disableSkippedValueLogging: boolean | 'true'; + retention: number | string; + ignoreBelowZero: boolean | 'true'; + customRetentionDuration: number | string; + maxLength: number | string; + round: number | string; + debounce: number | string; +} +export interface SqlAdapterConfigTyped { + connLink: string; + debounce: number; + retention: number; + host: string; + port: number; + user: string; + password: string; + dbtype: DbType; + fileName: string; + requestInterval: number; + encrypt: boolean; + round: number | null; + dbname: string; + multiRequests: boolean; + maxConnections: number; + changesRelogInterval: number; + changesMinDelta: number; + writeNulls: boolean; + doNotCreateDatabase: boolean; + maxLength: number; + blockTime: number; + debounceTime: number; + disableSkippedValueLogging: boolean; + enableDebugLogs: boolean; + rejectUnauthorized: boolean; + customRetentionDuration: number; + dockerMysql: { + enabled?: boolean; + bind?: string; + stopIfInstanceStopped?: boolean; + port?: string | number; + autoImageUpdate?: boolean; + rootPassword?: string; + }; + dockerPhpMyAdmin: { + enabled?: boolean; + bind?: string; + stopIfInstanceStopped?: boolean; + port?: string | number; + autoImageUpdate?: boolean; + absoluteUri?: string; + }; +} + +export interface SqlAdapterConfig extends SqlAdapterConfigTyped { + connLink: string; + debounce: number | string; + retention: number | string; + host: string; + port: number | string; + user: string; + password: string; + dbtype: DbType; + fileName: string; + requestInterval: number | string; + encrypt: boolean; + round: number | string | null; + dbname: string; + multiRequests: boolean; + maxConnections: number | string; + changesRelogInterval: number | string; + changesMinDelta: number | string; + writeNulls: boolean; + doNotCreateDatabase: boolean; + maxLength: number | string; + blockTime: number | string; + debounceTime: number | string; + disableSkippedValueLogging: boolean; + enableDebugLogs: boolean; + rejectUnauthorized: boolean; + customRetentionDuration: number | string; +} diff --git a/test/lib/setup.js b/test/lib/setup.js index 93ee17d9..4e79baa3 100644 --- a/test/lib/setup.js +++ b/test/lib/setup.js @@ -1,13 +1,12 @@ -/* jshint -W097 */// jshint strict:false +/* jshint -W097 */ // jshint strict:false /*jslint node: true */ // check if tmp directory exists -const fs = require('fs'); -const path = require('path'); -const child_process = require('child_process'); -const rootDir = path.normalize(__dirname + '/../../'); -const pkg = require(rootDir + 'package.json'); -const debug = typeof v8debug === 'object'; -pkg.main = pkg.main || 'main.js'; +const fs = require('node:fs'); +const path = require('node:path'); +const child_process = require('node:child_process'); +const rootDir = path.normalize(`${__dirname}/../../`); +const pkg = require(`${rootDir}package.json`); +const debug = typeof v8debug === 'object'; let JSONLDB; @@ -23,7 +22,7 @@ function getAppName() { function loadJSONLDB() { if (!JSONLDB) { const dbPath = require.resolve('@alcalzone/jsonl-db', { - paths: [rootDir + 'tmp/node_modules', rootDir, rootDir + 'tmp/node_modules/' + appName + '.js-controller'] + paths: [rootDir + 'tmp/node_modules', rootDir, rootDir + 'tmp/node_modules/' + appName + '.js-controller'], }); console.log('JSONLDB path: ' + dbPath); try { @@ -45,21 +44,19 @@ let pid = null; let systemConfig = null; function copyFileSync(source, target) { - let targetFile = target; //if target is a directory a new file with the same name will be created if (fs.existsSync(target)) { - if ( fs.lstatSync( target ).isDirectory() ) { + if (fs.lstatSync(target).isDirectory()) { targetFile = path.join(target, path.basename(source)); } } try { fs.writeFileSync(targetFile, fs.readFileSync(source)); - } - catch (err) { - console.log('file copy error: ' +source +' -> ' + targetFile + ' (error ignored)'); + } catch (err) { + console.log('file copy error: ' + source + ' -> ' + targetFile + ' (error ignored)'); } } @@ -234,7 +231,6 @@ async function checkIsAdapterInstalled(cb, counter, customName) { } else { console.error('checkIsAdapterInstalled: No objects file found in datadir ' + dataDir); } - } catch (err) { console.log('checkIsAdapterInstalled: catch ' + err); } @@ -244,7 +240,7 @@ async function checkIsAdapterInstalled(cb, counter, customName) { if (cb) cb('Cannot install'); } else { console.log('checkIsAdapterInstalled: wait...'); - setTimeout(function() { + setTimeout(function () { checkIsAdapterInstalled(cb, counter + 1); }, 1000); } @@ -288,20 +284,17 @@ async function checkIsControllerInstalled(cb, counter) { }, 100); return; } - } else { console.error('checkIsControllerInstalled: No objects file found in datadir ' + dataDir); } - } catch (err) { - - } + } catch (err) {} if (counter > 20) { console.log('checkIsControllerInstalled: Cannot install!'); if (cb) cb('Cannot install'); } else { console.log('checkIsControllerInstalled: wait...'); - setTimeout(function() { + setTimeout(function () { checkIsControllerInstalled(cb, counter + 1); }, 1000); } @@ -318,8 +311,8 @@ function installAdapter(customName, cb) { // make first install if (debug) { child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', { - cwd: rootDir + 'tmp', - stdio: [0, 1, 2] + cwd: rootDir + 'tmp', + stdio: [0, 1, 2], }); checkIsAdapterInstalled(function (error) { if (error) console.error(error); @@ -329,8 +322,8 @@ function installAdapter(customName, cb) { } else { // add controller const _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], { - cwd: rootDir + 'tmp', - stdio: [0, 1, 2, 'ipc'] + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'], }); waitForEnd(_pid, function () { @@ -364,12 +357,20 @@ function waitForEnd(_pid, cb) { function installJsController(cb) { console.log('installJsController...'); - if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') || - !fs.existsSync(rootDir + 'tmp/' + appName + '-data')) { + if ( + !fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') || + !fs.existsSync(rootDir + 'tmp/' + appName + '-data') + ) { // try to detect appName.js-controller in node_modules/appName.js-controller // travis CI installs js-controller into node_modules if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) { - console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"'); + console.log( + 'installJsController: no js-controller => copy it from "' + + rootDir + + 'node_modules/' + + appName + + '.js-controller"', + ); // copy all // stop controller console.log('Stop controller if running...'); @@ -378,12 +379,12 @@ function installJsController(cb) { // start controller _pid = child_process.exec('node ' + appName + '.js stop', { cwd: rootDir + 'node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2] + stdio: [0, 1, 2], }); } else { _pid = child_process.fork(appName + '.js', ['stop'], { - cwd: rootDir + 'node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2, 'ipc'] + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'], }); } @@ -392,9 +393,12 @@ function installJsController(cb) { if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp'); if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules'); - if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){ + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) { console.log('Copy js-controller...'); - copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/'); + copyFolderRecursiveSync( + rootDir + 'node_modules/' + appName + '.js-controller', + rootDir + 'tmp/node_modules/', + ); } console.log('Setup js-controller...'); @@ -403,12 +407,12 @@ function installJsController(cb) { // start controller _pid = child_process.exec('node ' + appName + '.js setup first --console', { cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2] + stdio: [0, 1, 2], }); } else { __pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], { - cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2, 'ipc'] + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'], }); } waitForEnd(__pid, function () { @@ -416,12 +420,15 @@ function installJsController(cb) { // change ports for object and state DBs const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); config.objects.port = 19001; - config.states.port = 19000; + config.states.port = 19000; // TEST WISE! //config.objects.type = 'jsonl'; //config.states.type = 'jsonl'; - fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + fs.writeFileSync( + rootDir + 'tmp/' + appName + '-data/' + appName + '.json', + JSON.stringify(config, null, 2), + ); console.log('Setup finished.'); copyAdapterToController(); @@ -437,8 +444,10 @@ function installJsController(cb) { // check if port 9000 is free, else admin adapter will be added to running instance const client = new require('net').Socket(); client.on('error', () => {}); - client.connect(9000, '127.0.0.1', function() { - console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.'); + client.connect(9000, '127.0.0.1', function () { + console.error( + 'Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.', + ); process.exit(0); }); @@ -448,8 +457,8 @@ function installJsController(cb) { console.log('installJsController: no js-controller => install dev build from npm'); child_process.execSync('npm install ' + appName + '.js-controller@dev --prefix ./ --production', { - cwd: rootDir + 'tmp/', - stdio: [0, 1, 2] + cwd: rootDir + 'tmp/', + stdio: [0, 1, 2], }); } else { console.log('Setup js-controller...'); @@ -458,12 +467,12 @@ function installJsController(cb) { // start controller child_process.exec('node ' + appName + '.js setup first', { cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2] + stdio: [0, 1, 2], }); } else { child_process.fork(appName + '.js', ['setup', 'first'], { - cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2, 'ipc'] + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'], }); } } @@ -474,8 +483,8 @@ function installJsController(cb) { if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) { _pid = child_process.fork(appName + '.js', ['stop'], { - cwd: rootDir + 'node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2, 'ipc'] + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'], }); } @@ -483,12 +492,15 @@ function installJsController(cb) { // change ports for object and state DBs const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); config.objects.port = 19001; - config.states.port = 19000; + config.states.port = 19000; // TEST WISE! //config.objects.type = 'jsonl'; //config.states.type = 'jsonl'; - fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + fs.writeFileSync( + rootDir + 'tmp/' + appName + '-data/' + appName + '.json', + JSON.stringify(config, null, 2), + ); copyAdapterToController(); @@ -511,7 +523,13 @@ function installJsController(cb) { function copyAdapterToController() { console.log('Copy adapter...'); // Copy adapter to tmp/node_modules/appName.adapter - copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']); + copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', [ + '.idea', + 'test', + 'tmp', + '.git', + appName + '.js-controller', + ]); console.log('Adapter copied.'); } @@ -527,7 +545,7 @@ function clearControllerLog() { files = []; fs.mkdirSync(dirPath); } - } catch(e) { + } catch (e) { console.error('Cannot read "' + dirPath + '"'); return; } @@ -556,7 +574,7 @@ function clearDB() { files = []; fs.mkdirSync(dirPath); } - } catch(e) { + } catch (e) { console.error('Cannot read "' + dirPath + '"'); return; } @@ -593,10 +611,10 @@ function setupController(cb) { objs = JSON.parse(objs); } catch (e) { console.log('ERROR reading/parsing system configuration. Ignore'); - objs = {'system.config': {}}; + objs = { 'system.config': {} }; } if (!objs || !objs['system.config']) { - objs = {'system.config': {}}; + objs = { 'system.config': {} }; } systemConfig = objs['system.config']; @@ -622,7 +640,7 @@ function setupController(cb) { } async function getSecret() { - var dataDir = rootDir + 'tmp/' + appName + '-data/'; + const dataDir = `${rootDir}tmp/${appName}-data/`; if (systemConfig) { return systemConfig.native.secret; @@ -632,13 +650,12 @@ async function getSecret() { try { objs = fs.readFileSync(dataDir + 'objects.json'); objs = JSON.parse(objs); - } - catch (e) { - console.warn("Could not load secret. Reason: " + e); + } catch (e) { + console.warn('Could not load secret. Reason: ' + e); return null; } if (!objs || !objs['system.config']) { - objs = {'system.config': {}}; + objs = { 'system.config': {} }; } return objs['system.config'].native.secre; @@ -656,12 +673,11 @@ async function getSecret() { } else { console.error('read secret: No objects file found in datadir ' + dataDir); } - } -function encrypt (key, value) { - var result = ''; - for (var i = 0; i < value.length; ++i) { +function encrypt(key, value) { + let result = ''; + for (let i = 0; i < value.length; ++i) { result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i)); } return result; @@ -684,13 +700,13 @@ function startAdapter(objects, states, callback) { // start controller pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', { cwd: rootDir + 'tmp', - stdio: [0, 1, 2] + stdio: [0, 1, 2], }); } else { // start controller pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], { - cwd: rootDir + 'tmp', - stdio: [0, 1, 2, 'ipc'] + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'], }); } } catch (error) { @@ -711,7 +727,7 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback } if (onStateChange === undefined) { - callback = onObjectChange; + callback = onObjectChange; onObjectChange = undefined; } @@ -728,19 +744,23 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback // rootDir + 'tmp/node_modules const objPath = require.resolve(`@iobroker/db-objects-${config.objects.type}`, { - paths: [ rootDir + 'tmp/node_modules', rootDir, rootDir + 'tmp/node_modules/' + appName + '.js-controller'] + paths: [ + rootDir + 'tmp/node_modules', + rootDir, + rootDir + 'tmp/node_modules/' + appName + '.js-controller', + ], }); console.log('Objects Path: ' + objPath); const Objects = require(objPath).Server; objects = new Objects({ connection: { - 'type': config.objects.type, - 'host': '127.0.0.1', - 'port': 19001, - 'user': '', - 'pass': '', - 'noFileCache': false, - 'connectTimeout': 2000 + type: config.objects.type, + host: '127.0.0.1', + port: 19001, + user: '', + pass: '', + noFileCache: false, + connectTimeout: 2000, }, logger: { silly: function (msg) { @@ -757,7 +777,7 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback }, error: function (msg) { console.error(msg); - } + }, }, connected: function () { isObjectConnected = true; @@ -773,12 +793,16 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback } } }, - change: onObjectChange + change: onObjectChange, }); // Just open in memory DB itself const statePath = require.resolve(`@iobroker/db-states-${config.states.type}`, { - paths: [ rootDir + 'tmp/node_modules', rootDir, rootDir + 'tmp/node_modules/' + appName + '.js-controller'] + paths: [ + rootDir + 'tmp/node_modules', + rootDir, + rootDir + 'tmp/node_modules/' + appName + '.js-controller', + ], }); console.log('States Path: ' + statePath); const States = require(statePath).Server; @@ -789,8 +813,8 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback port: 19000, options: { auth_pass: null, - retry_max_delay: 15000 - } + retry_max_delay: 15000, + }, }, logger: { silly: function (msg) { @@ -807,7 +831,7 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback }, error: function (msg) { console.log(msg); - } + }, }, connected: function () { isStatesConnected = true; @@ -823,7 +847,7 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback } } }, - change: onStateChange + change: onStateChange, }); } catch (err) { console.log(err); @@ -876,9 +900,9 @@ function stopController(cb) { if (objects) { console.log('Set system.adapter.' + pkg.name + '.0'); objects.setObject('system.adapter.' + pkg.name + '.0', { - common:{ - enabled: false - } + common: { + enabled: false, + }, }); } @@ -957,15 +981,15 @@ async function getAdapterConfig(instance) { if (typeof module !== undefined && module.parent) { module.exports.getAdapterConfig = getAdapterConfig; module.exports.setAdapterConfig = setAdapterConfig; - module.exports.startController = startController; - module.exports.stopController = stopController; - module.exports.setupController = setupController; - module.exports.stopAdapter = stopAdapter; - module.exports.startAdapter = startAdapter; - module.exports.installAdapter = installAdapter; - module.exports.appName = appName; - module.exports.adapterName = adapterName; - module.exports.adapterStarted = adapterStarted; - module.exports.getSecret = getSecret; - module.exports.encrypt = encrypt; + module.exports.startController = startController; + module.exports.stopController = stopController; + module.exports.setupController = setupController; + module.exports.stopAdapter = stopAdapter; + module.exports.startAdapter = startAdapter; + module.exports.installAdapter = installAdapter; + module.exports.appName = appName; + module.exports.adapterName = adapterName; + module.exports.adapterStarted = adapterStarted; + module.exports.getSecret = getSecret; + module.exports.encrypt = encrypt; } diff --git a/test/lib/testcases.js b/test/lib/testcases.js index 87641935..9f7e987a 100644 --- a/test/lib/testcases.js +++ b/test/lib/testcases.js @@ -19,90 +19,88 @@ async function preInit(_objects, _states, sendTo, adapterShortName) { common: { type: 'number', role: 'state', - custom: {} + custom: {}, }, - type: 'state' + type: 'state', }; obj.common.custom[instanceName] = { enabled: true, - changesOnly: true, - debounce: 0, - retention: 31536000, - maxLength: 3, - changesMinDelta: 0.5 + changesOnly: true, + debounce: 0, + retention: 31536000, + maxLength: 3, + changesMinDelta: 0.5, }; await objects.setObjectAsync(`${instanceName}.testValue`, obj); obj = { common: { type: 'number', role: 'state', - custom: {} + custom: {}, }, - type: 'state' + type: 'state', }; obj.common.custom[instanceName] = { enabled: true, - changesOnly: true, + changesOnly: true, changesRelogInterval: 10, - debounceTime: 500, - retention: 31536000, - maxLength: 3, + debounceTime: 500, + retention: 31536000, + maxLength: 3, changesMinDelta: 0.5, ignoreBelowNumber: -1, ignoreAboveNumber: 100, - ignoreZero: true, - aliasId: `${instanceName}.testValueDebounce alias` + ignoreZero: true, + aliasId: `${instanceName}.testValueDebounce alias`, }; await objects.setObjectAsync(`${instanceName}.testValueDebounce`, obj); obj = { common: { type: 'number', role: 'state', - custom: {} + custom: {}, }, - type: 'state' + type: 'state', }; obj.common.custom[instanceName] = { enabled: true, - changesOnly: true, + changesOnly: true, changesRelogInterval: 10, - debounceTime: 500, - retention: 31536000, - maxLength: 0, + debounceTime: 500, + retention: 31536000, + maxLength: 0, changesMinDelta: 0.5, disableSkippedValueLogging: true, ignoreBelowZero: true, ignoreAboveNumber: 100, - storageType: 'Number' + storageType: 'Number', }; await objects.setObjectAsync(`${instanceName}.testValueDebounceRaw`, obj); obj = { common: { type: 'number', role: 'state', - custom: {} + custom: {}, }, - type: 'state' + type: 'state', }; obj.common.custom[instanceName] = { enabled: true, - changesOnly: true, + changesOnly: true, changesRelogInterval: 10, - debounceTime: 0, - blockTime: 1500, - retention: 31536000, - maxLength: 3, - changesMinDelta: 0.5, + debounceTime: 0, + blockTime: 1500, + retention: 31536000, + maxLength: 3, + changesMinDelta: 0.5, ignoreBelowNumber: -1, - ignoreAboveNumber: 100 + ignoreAboveNumber: 100, }; await objects.setObjectAsync(`${instanceName}.testValueBlocked`, obj); await objects.setObjectAsync('system.adapter.test.0', { - common: { - - }, - type: 'instance' + common: {}, + type: 'instance', }); states.subscribeMessage('system.adapter.test.0'); } @@ -112,36 +110,44 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti if (writeNulls) adapterShortName += '-writeNulls'; if (assumeExistingData) adapterShortName += '-existing'; - it(`Test ${adapterShortName}: Setup test objects after start`, function(done) { + it(`Test ${adapterShortName}: Setup test objects after start`, function (done) { this.timeout(5000); - objects.setObject(`${instanceName}.testValue2`, { + objects.setObject( + `${instanceName}.testValue2`, + { common: { type: 'number', - role: 'state' + role: 'state', }, - type: 'state' + type: 'state', }, function () { - sendTo(instanceName, 'enableHistory', { - id: `${instanceName}.testValue2`, - options: { - changesOnly: true, - debounce: 0, - retention: 31536000, - maxLength: 0, - changesMinDelta: 0.5, - aliasId: `${instanceName}.testValue2-alias` - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 2000); - }); - }); + sendTo( + instanceName, + 'enableHistory', + { + id: `${instanceName}.testValue2`, + options: { + changesOnly: true, + debounce: 0, + retention: 31536000, + maxLength: 0, + changesMinDelta: 0.5, + aliasId: `${instanceName}.testValue2-alias`, + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 2000); + }, + ); + }, + ); }); it(`Test ${adapterShortName}: Check Enabled Points after Enable`, function (done) { @@ -158,62 +164,94 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti this.timeout(25000); now = Date.now(); - states.setState(`${instanceName}.testValue`, {val: 1, ts: now + 1000}, function (err) { + states.setState(`${instanceName}.testValue`, { val: 1, ts: now + 1000 }, function (err) { if (err) { console.log(err); } setTimeout(function () { - states.setState(`${instanceName}.testValue`, {val: 2, ts: now + 10000}, function (err) { + states.setState(`${instanceName}.testValue`, { val: 2, ts: now + 10000 }, function (err) { if (err) { console.log(err); } setTimeout(function () { - states.setState(`${instanceName}.testValue`, {val: 2, ts: now + 13000}, function (err) { + states.setState(`${instanceName}.testValue`, { val: 2, ts: now + 13000 }, function (err) { if (err) { console.log(err); } setTimeout(function () { - states.setState(`${instanceName}.testValue`, {val: 2, ts: now + 15000}, function (err) { - if (err) { - console.log(err); - } - setTimeout(function () { - states.setState(`${instanceName}.testValue`, {val: 2.2, ts: now + 16000}, function (err) { - if (err) { - console.log(err); - } - setTimeout(function () { - states.setState(`${instanceName}.testValue`, {val: 2.5, ts: now + 17000}, function (err) { + states.setState( + `${instanceName}.testValue`, + { val: 2, ts: now + 15000 }, + function (err) { + if (err) { + console.log(err); + } + setTimeout(function () { + states.setState( + `${instanceName}.testValue`, + { val: 2.2, ts: now + 16000 }, + function (err) { if (err) { console.log(err); } setTimeout(function () { - states.setState(`${instanceName}.testValue`, {val: '+003.00', ts: now + 19000}, function (err) { - if (err) { - console.log(err); - } - setTimeout(function () { - states.setState(`${instanceName}.testValue2`, {val: 1, ts: now + 12000}, function (err) { - if (err) { - console.log(err); - } - setTimeout(function () { - states.setState(`${instanceName}.testValue2`, {val: 3, ts: now + 19000}, function (err) { + states.setState( + `${instanceName}.testValue`, + { val: 2.5, ts: now + 17000 }, + function (err) { + if (err) { + console.log(err); + } + setTimeout(function () { + states.setState( + `${instanceName}.testValue`, + { val: '+003.00', ts: now + 19000 }, + function (err) { if (err) { console.log(err); } - setTimeout(done, 1000); - }); - }, 100); - }); - }, 100); - }); + setTimeout(function () { + states.setState( + `${instanceName}.testValue2`, + { val: 1, ts: now + 12000 }, + function (err) { + if (err) { + console.log(err); + } + setTimeout(function () { + states.setState( + `${instanceName}.testValue2`, + { + val: 3, + ts: now + 19000, + }, + function (err) { + if (err) { + console.log( + err, + ); + } + setTimeout( + done, + 1000, + ); + }, + ); + }, 100); + }, + ); + }, 100); + }, + ); + }, 100); + }, + ); }, 100); - }); - }, 100); - }); - }, 100); - }); + }, + ); + }, 100); + }, + ); }, 100); }); }, 100); @@ -225,153 +263,188 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti it(`Test ${adapterShortName}: Read values from DB using GetHistory`, function (done) { this.timeout(25000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: now, - end: now + 30000, - count: 50, - aggregate: 'none' - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.at.least(4); - var found = 0; - for (var i = 0; i < result.result.length; i++) { - if (result.result[i].val >= 1 && result.result[i].val <= 3) found ++; - } - expect(found).to.be.equal(5); // additionally null value by start of adapter. - - sendTo(instanceName, 'getHistory', { + sendTo( + instanceName, + 'getHistory', + { id: `${instanceName}.testValue`, options: { - start: now, - end: now + 30000, - count: 2, - aggregate: 'none' - } - }, function (result) { + start: now, + end: now + 30000, + count: 50, + aggregate: 'none', + }, + }, + function (result) { console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.equal(2); - var found = 0; - for (var i = 0; i < result.result.length; i++) { - if (result.result[i].val >= 1 && result.result[i].val <= 3) found ++; + expect(result.result.length).to.be.at.least(4); + let found = 0; + for (let i = 0; i < result.result.length; i++) { + if (result.result[i].val >= 1 && result.result[i].val <= 3) found++; } - expect(found).to.be.equal(2); - expect(result.result[0].id).to.be.undefined; + expect(found).to.be.equal(5); // additionally, null value by start of adapter. - const latestTs = result.result[result.result.length - 1].ts; + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: now, + end: now + 30000, + count: 2, + aggregate: 'none', + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.equal(2); + let found = 0; + for (let i = 0; i < result.result.length; i++) { + if (result.result[i].val >= 1 && result.result[i].val <= 3) found++; + } + expect(found).to.be.equal(2); + expect(result.result[0].id).to.be.undefined; - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: now, - end: now + 30000, - count: 2, - aggregate: 'none', - addId: true, - returnNewestEntries: true - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.equal(2); - var found = 0; - for (var i = 0; i < result.result.length; i++) { - if (result.result[i].val >= 2.5 && result.result[i].val <= 3) found ++; - } - expect(found).to.be.equal(2); - expect(result.result[0].ts >= latestTs).to.be.true; - expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); - done(); - }); - }); - }); + const latestTs = result.result[result.result.length - 1].ts; + + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: now, + end: now + 30000, + count: 2, + aggregate: 'none', + addId: true, + returnNewestEntries: true, + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.equal(2); + let found = 0; + for (let i = 0; i < result.result.length; i++) { + if (result.result[i].val >= 2.5 && result.result[i].val <= 3) found++; + } + expect(found).to.be.equal(2); + expect(result.result[0].ts >= latestTs).to.be.true; + expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); + done(); + }, + ); + }, + ); + }, + ); }); it(`Test ${adapterShortName}: Read average from DB using GetHistory`, function (done) { this.timeout(25000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: now + 100, - end: now + 30001, - count: 2, - aggregate: 'average', - ignoreNull: true, - addId: true - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - if (instanceName !== 'influxdb.0') { - expect(result.result.length).to.be.equal(4); - expect(result.result[1].val).to.be.equal(1.5); - expect(result.result[2].val).to.be.equal(2.57); - expect(result.result[3].val).to.be.equal(2.57); - } else { - expect(result.result.length).to.be.within(4,5); - expect(result.result[1].val).to.be.within(1, 1.5); - expect(result.result[2].val).to.be.within(2, 3); - expect(result.result[3].val).to.be.within(2, 3); - } - expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); - done(); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: now + 100, + end: now + 30001, + count: 2, + aggregate: 'average', + ignoreNull: true, + addId: true, + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + if (instanceName !== 'influxdb.0') { + expect(result.result.length).to.be.equal(4); + expect(result.result[1].val).to.be.equal(1.5); + expect(result.result[2].val).to.be.equal(2.57); + expect(result.result[3].val).to.be.equal(2.57); + } else { + expect(result.result.length).to.be.within(4, 5); + expect(result.result[1].val).to.be.within(1, 1.5); + expect(result.result[2].val).to.be.within(2, 3); + expect(result.result[3].val).to.be.within(2, 3); + } + expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); + done(); + }, + ); }); it(`Test ${adapterShortName}: Read minmax values from DB using GetHistory`, function (done) { this.timeout(10000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: now - 30000, - end: now + 30000, - count: 4, - aggregate: 'minmax', - addId: true - } - }, result => { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.at.least(4); - expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); - done(); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: now - 30000, + end: now + 30000, + count: 4, + aggregate: 'minmax', + addId: true, + }, + }, + result => { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.at.least(4); + expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); + done(); + }, + ); }); it(`Test ${adapterShortName}: Read values from DB using GetHistory for aliased testValue2`, function (done) { this.timeout(25000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue2`, - options: { - start: now, - end: now + 30000, - count: 50, - aggregate: 'none' - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.equal(2); - - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue2-alias`, + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue2`, options: { - start: now, - end: now + 30000, - count: 50, - aggregate: 'none' - } - }, function (result2) { - console.log(JSON.stringify(result2.result, null, 2)); - expect(result2.result.length).to.be.equal(2); - for (var i = 0; i < result2.result.length; i++) { - expect(result2.result[i].val).to.be.equal(result.result[i].val); - } + start: now, + end: now + 30000, + count: 50, + aggregate: 'none', + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.equal(2); - done(); - }); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue2-alias`, + options: { + start: now, + end: now + 30000, + count: 50, + aggregate: 'none', + }, + }, + function (result2) { + console.log(JSON.stringify(result2.result, null, 2)); + expect(result2.result.length).to.be.equal(2); + for (let i = 0; i < result2.result.length; i++) { + expect(result2.result[i].val).to.be.equal(result.result[i].val); + } + + done(); + }, + ); + }, + ); }); function delay(ms) { @@ -380,42 +453,42 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti async function logSampleData(stateId, waitMultiplier) { if (!waitMultiplier) waitMultiplier = 1; - await states.setStateAsync(stateId, {val: 1}); // expect logged + await states.setStateAsync(stateId, { val: 1 }); // expect logged await delay(600 * waitMultiplier); - await states.setStateAsync(stateId, {val: 2}); // Expect not logged debounce + await states.setStateAsync(stateId, { val: 2 }); // Expect not logged debounce await delay(20 * waitMultiplier); - await states.setStateAsync(stateId, {val: 2.1}); // Expect not logged debounce + await states.setStateAsync(stateId, { val: 2.1 }); // Expect not logged debounce await delay(20 * waitMultiplier); - await states.setStateAsync(stateId, {val: 1.5}); // Expect not logged debounce + await states.setStateAsync(stateId, { val: 1.5 }); // Expect not logged debounce await delay(20 * waitMultiplier); - await states.setStateAsync(stateId, {val: 2.3}); // Expect not logged debounce + await states.setStateAsync(stateId, { val: 2.3 }); // Expect not logged debounce await delay(20 * waitMultiplier); - await states.setStateAsync(stateId, {val: 2.5}); // Expect not logged debounce + await states.setStateAsync(stateId, { val: 2.5 }); // Expect not logged debounce await delay(600 * waitMultiplier); - await states.setStateAsync(stateId, {val: 2.9}); // Expect logged skipped + await states.setStateAsync(stateId, { val: 2.9 }); // Expect logged skipped await delay(600 * waitMultiplier); - await states.setStateAsync(stateId, {val: 3.0}); // Expect logged + await states.setStateAsync(stateId, { val: 3.0 }); // Expect logged await delay(600 * waitMultiplier); - await states.setStateAsync(stateId, {val: 4}); // Expect logged + await states.setStateAsync(stateId, { val: 4 }); // Expect logged await delay(600 * waitMultiplier); - await states.setStateAsync(stateId, {val: 4.4}); // expect logged skipped + await states.setStateAsync(stateId, { val: 4.4 }); // expect logged skipped await delay(600 * waitMultiplier); - await states.setStateAsync(stateId, {val: 5}); // expect logged + await states.setStateAsync(stateId, { val: 5 }); // expect logged await delay(20 * waitMultiplier); - await states.setStateAsync(stateId, {val: 5}); // expect not logged debounce + await states.setStateAsync(stateId, { val: 5 }); // expect not logged debounce await delay(600 * waitMultiplier); - await states.setStateAsync(stateId, {val: 5}); // expect logged skipped + await states.setStateAsync(stateId, { val: 5 }); // expect logged skipped await delay(600 * waitMultiplier); - await states.setStateAsync(stateId, {val: 6}); // expect logged + await states.setStateAsync(stateId, { val: 6 }); // expect logged await delay(10100); for (let i = 1; i < 10; i++) { - await states.setStateAsync(stateId, {val: 6 + i * 0.05}); // expect logged skipped + await states.setStateAsync(stateId, { val: 6 + i * 0.05 }); // expect logged skipped await delay(70 * waitMultiplier); } - await states.setStateAsync(stateId, {val: 7}); // expect logged + await states.setStateAsync(stateId, { val: 7 }); // expect logged await delay(5000); - await states.setStateAsync(stateId, {val: -5}); // expect not logged, too low - await states.setStateAsync(stateId, {val: 101}); // expect not logged, too high + await states.setStateAsync(stateId, { val: -5 }); // expect not logged, too low + await states.setStateAsync(stateId, { val: 101 }); // expect not logged, too high await delay(7000); } @@ -431,29 +504,34 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti } return new Promise(resolve => { - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValueDebounceRaw`, - options: { - start: now, - end: Date.now(), - count: 50, - aggregate: 'none' - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.at.least(9); - expect(result.result[0].val).to.be.equal(1); - expect(result.result[1].val).to.be.equal(2.5); - expect(result.result[2].val).to.be.equal(3.0); - expect(result.result[3].val).to.be.equal(4); - expect(result.result[4].val).to.be.equal(5); - expect(result.result[5].val).to.be.equal(6); - expect(result.result[6].val).to.be.equal(6); - expect(result.result[7].val).to.be.equal(7); - expect(result.result[8].val).to.be.equal(7); - - setTimeout(resolve, 2000); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValueDebounceRaw`, + options: { + start: now, + end: Date.now(), + count: 50, + aggregate: 'none', + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.at.least(9); + expect(result.result[0].val).to.be.equal(1); + expect(result.result[1].val).to.be.equal(2.5); + expect(result.result[2].val).to.be.equal(3.0); + expect(result.result[3].val).to.be.equal(4); + expect(result.result[4].val).to.be.equal(5); + expect(result.result[5].val).to.be.equal(6); + expect(result.result[6].val).to.be.equal(6); + expect(result.result[7].val).to.be.equal(7); + expect(result.result[8].val).to.be.equal(7); + + setTimeout(resolve, 2000); + }, + ); }); }); @@ -469,145 +547,171 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti } return new Promise(resolve => { - - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValueDebounce alias`, - options: { - start: now, - end: Date.now(), - count: 50, - aggregate: 'none' - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.at.least(12); - - const expectedVals = [1, 2.5, 3, 4, 5, 5, 6, 7, 7]; - let expectedId = 0; - for (let i = 0; i < result.result.length; i++) { - console.log(`${i}: check ${result.result[i].val} vs ${expectedVals[expectedId]} (${expectedId})`); - expect(result.result[i].val).to.be.lessThanOrEqual(expectedVals[expectedId]); - if (result.result[i].val === expectedVals[expectedId] && expectedId < expectedVals.length - 1) { - expectedId++; + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValueDebounce alias`, + options: { + start: now, + end: Date.now(), + count: 50, + aggregate: 'none', + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.at.least(12); + + const expectedVals = [1, 2.5, 3, 4, 5, 5, 6, 7, 7]; + let expectedId = 0; + for (let i = 0; i < result.result.length; i++) { + console.log( + `${i}: check ${result.result[i].val} vs ${expectedVals[expectedId]} (${expectedId})`, + ); + expect(result.result[i].val).to.be.lessThanOrEqual(expectedVals[expectedId]); + if (result.result[i].val === expectedVals[expectedId] && expectedId < expectedVals.length - 1) { + expectedId++; + } } - } - expect(expectedId).to.be.equal(expectedVals.length - 1); + expect(expectedId).to.be.equal(expectedVals.length - 1); - resolve(); - }); + resolve(); + }, + ); }); }); it(`Test ${adapterShortName}: Read percentile 50+95 values from DB using GetHistory`, function (done) { this.timeout(15000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValueDebounce alias`, - options: { - start: now, - end: Date.now(), - count: 1, - aggregate: 'percentile', - percentile: 50, - removeBorderValues: true, - addId: true - } - }, result => { - console.log(JSON.stringify(result.result, null, 2)); - if (instanceName !== 'influxdb.0') { - expect(result.result.length).to.be.equal(1); - expect(result.result[0].val).to.be.equal(5); - expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); - } else { - if (process.env.INFLUXDB2) { - expect(result.result.length).to.be.within(1,3); - expect(result.result[1] ? result.result[1].val : result.result[0].val).to.be.within(5, 7); - } else { - expect(result.result.length).to.be.within(1,2); - expect(result.result[1] ? result.result[1].val : result.result[0].val).to.be.within(5, 7); - } - expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); - } - - sendTo(instanceName, 'getHistory', { + sendTo( + instanceName, + 'getHistory', + { id: `${instanceName}.testValueDebounce alias`, options: { - start: now, - end: Date.now(), - count: 1, + start: now, + end: Date.now(), + count: 1, aggregate: 'percentile', - percentile: 95, + percentile: 50, removeBorderValues: true, - addId: true - } - }, result => { + addId: true, + }, + }, + result => { console.log(JSON.stringify(result.result, null, 2)); if (instanceName !== 'influxdb.0') { expect(result.result.length).to.be.equal(1); - expect(result.result[0].val).to.be.equal(7); + expect(result.result[0].val).to.be.equal(5); + expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); } else { - expect(result.result.length).to.be.within(1,3); - expect(result.result[result.result.length - 1].val).to.be.equal(7); + if (process.env.INFLUXDB2) { + expect(result.result.length).to.be.within(1, 3); + expect(result.result[1] ? result.result[1].val : result.result[0].val).to.be.within(5, 7); + } else { + expect(result.result.length).to.be.within(1, 2); + expect(result.result[1] ? result.result[1].val : result.result[0].val).to.be.within(5, 7); + } expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); } - expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); - done(); - }); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValueDebounce alias`, + options: { + start: now, + end: Date.now(), + count: 1, + aggregate: 'percentile', + percentile: 95, + removeBorderValues: true, + addId: true, + }, + }, + result => { + console.log(JSON.stringify(result.result, null, 2)); + if (instanceName !== 'influxdb.0') { + expect(result.result.length).to.be.equal(1); + expect(result.result[0].val).to.be.equal(7); + } else { + expect(result.result.length).to.be.within(1, 3); + expect(result.result[result.result.length - 1].val).to.be.equal(7); + expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); + } + + expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); + done(); + }, + ); + }, + ); }); it(`Test ${adapterShortName}: Read integral from DB using GetHistory`, function (done) { this.timeout(25000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValueDebounce`, - options: { - start: now, - end: Date.now(), - count: 5, - aggregate: 'integral', - integralUnit: 5, - removeBorderValues: true, - addId: true - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - if (instanceName !== 'influxdb.0') { - expect(result.result.length).to.be.equal(5); - } else { - expect(result.result.length).to.be.within(3, 5); - } - expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); - done(); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValueDebounce`, + options: { + start: now, + end: Date.now(), + count: 5, + aggregate: 'integral', + integralUnit: 5, + removeBorderValues: true, + addId: true, + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + if (instanceName !== 'influxdb.0') { + expect(result.result.length).to.be.equal(5); + } else { + expect(result.result.length).to.be.within(3, 5); + } + expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); + done(); + }, + ); }); it(`Test ${adapterShortName}: Read linear integral from DB using GetHistory`, function (done) { this.timeout(25000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValueDebounce`, - options: { - start: now, - end: Date.now(), - count: 5, - aggregate: 'integral', - integralUnit: 5, - integralInterpolation: 'linear', - removeBorderValues: true, - addId: true - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - if (instanceName !== 'influxdb.0') { - expect(result.result.length).to.be.equal(5); - } else { - expect(result.result.length).to.be.within(3, 6); - } - expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); - done(); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValueDebounce`, + options: { + start: now, + end: Date.now(), + count: 5, + aggregate: 'integral', + integralUnit: 5, + integralInterpolation: 'linear', + removeBorderValues: true, + addId: true, + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + if (instanceName !== 'influxdb.0') { + expect(result.result.length).to.be.equal(5); + } else { + expect(result.result.length).to.be.within(3, 6); + } + expect(result.result[0].id).to.be.equal(`${instanceName}.testValueDebounce alias`); + done(); + }, + ); }); it(`Test ${adapterShortName}: Write with 1s block values into DB`, async function () { @@ -622,30 +726,34 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti } return new Promise(resolve => { - - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValueBlocked`, - options: { - start: now, - end: Date.now(), - count: 50, - aggregate: 'none' - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.at.least(9); - expect(result.result[0].val).to.be.equal(1); - expect(result.result[1].val).to.be.at.least(2.3); - expect(result.result[2].val).to.be.equal(4); - expect(result.result[3].val).to.be.equal(5); - expect(result.result[4].val).to.be.equal(6); - expect(result.result[5].val).to.be.equal(6); - expect(result.result[6].val).to.be.equal(6.45); - expect(result.result[7].val).to.be.equal(7); - expect(result.result[8].val).to.be.equal(7); - - resolve(); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValueBlocked`, + options: { + start: now, + end: Date.now(), + count: 50, + aggregate: 'none', + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.at.least(9); + expect(result.result[0].val).to.be.equal(1); + expect(result.result[1].val).to.be.at.least(2.3); + expect(result.result[2].val).to.be.equal(4); + expect(result.result[3].val).to.be.equal(5); + expect(result.result[4].val).to.be.equal(6); + expect(result.result[5].val).to.be.equal(6); + expect(result.result[6].val).to.be.equal(6.45); + expect(result.result[7].val).to.be.equal(7); + expect(result.result[8].val).to.be.equal(7); + + resolve(); + }, + ); }); }); @@ -658,252 +766,336 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti const nowSampleI24 = Date.now() - 25 * 60 * 60 * 1000; return new Promise(resolve => { + sendTo( + instanceName, + 'storeState', + { + id: `${instanceName}.testValue`, + state: [ + { val: 2.064, ack: true, ts: nowSampleI1 }, // + { val: 2.116, ack: true, ts: nowSampleI1 + 6 * 60 * 1000 }, + { val: 2.028, ack: true, ts: nowSampleI1 + 12 * 60 * 1000 }, + { val: 2.126, ack: true, ts: nowSampleI1 + 18 * 60 * 1000 }, + { val: 2.041, ack: true, ts: nowSampleI1 + 24 * 60 * 1000 }, + { val: 2.051, ack: true, ts: nowSampleI1 + 30 * 60 * 1000 }, + + { val: -2, ack: true, ts: nowSampleI21 }, // 10s none = 50.0 + { val: 10, ack: true, ts: nowSampleI21 + 10 * 1000 }, + { val: 7, ack: true, ts: nowSampleI21 + 20 * 1000 }, + { val: 17, ack: true, ts: nowSampleI21 + 30 * 1000 }, + { val: 15, ack: true, ts: nowSampleI21 + 40 * 1000 }, + { val: 4, ack: true, ts: nowSampleI21 + 50 * 1000 }, + + { val: 19, ack: true, ts: nowSampleI22 }, // 10s none = 43 + { val: 4, ack: true, ts: nowSampleI22 + 10 * 1000 }, + { val: -3, ack: true, ts: nowSampleI22 + 20 * 1000 }, + { val: 19, ack: true, ts: nowSampleI22 + 30 * 1000 }, + { val: 13, ack: true, ts: nowSampleI22 + 40 * 1000 }, + { val: 1, ack: true, ts: nowSampleI22 + 50 * 1000 }, + + { val: -2, ack: true, ts: nowSampleI23 }, // 10s linear = 25 + { val: 7, ack: true, ts: nowSampleI23 + 20 * 1000 }, + { val: 4, ack: true, ts: nowSampleI23 + 50 * 1000 }, + + { val: 4, ack: true, ts: nowSampleI24 + 10 * 1000 }, // 10s linear = 32.5 + { val: -3, ack: true, ts: nowSampleI24 + 20 * 1000 }, + { val: 19, ack: true, ts: nowSampleI24 + 30 * 1000 }, + { val: 1, ack: true, ts: nowSampleI24 + 50 * 1000 }, + ], + }, + function (result) { + expect(result.success).to.be.true; - sendTo(instanceName, 'storeState', { - id: `${instanceName}.testValue`, - state: [ - {val: 2.064, ack: true, ts: nowSampleI1}, // - {val: 2.116, ack: true, ts: nowSampleI1 + 6 * 60 * 1000}, - {val: 2.028, ack: true, ts: nowSampleI1 + 12 * 60 * 1000}, - {val: 2.126, ack: true, ts: nowSampleI1 + 18 * 60 * 1000}, - {val: 2.041, ack: true, ts: nowSampleI1 + 24 * 60 * 1000}, - {val: 2.051, ack: true, ts: nowSampleI1 + 30 * 60 * 1000}, - - {val: -2, ack: true, ts: nowSampleI21}, // 10s none = 50.0 - {val: 10, ack: true, ts: nowSampleI21 + 10 * 1000}, - {val: 7, ack: true, ts: nowSampleI21 + 20 * 1000}, - {val: 17, ack: true, ts: nowSampleI21 + 30 * 1000}, - {val: 15, ack: true, ts: nowSampleI21 + 40 * 1000}, - {val: 4, ack: true, ts: nowSampleI21 + 50 * 1000}, - - {val: 19, ack: true, ts: nowSampleI22}, // 10s none = 43 - {val: 4, ack: true, ts: nowSampleI22 + 10 * 1000}, - {val: -3, ack: true, ts: nowSampleI22 + 20 * 1000}, - {val: 19, ack: true, ts: nowSampleI22 + 30 * 1000}, - {val: 13, ack: true, ts: nowSampleI22 + 40 * 1000}, - {val: 1, ack: true, ts: nowSampleI22 + 50 * 1000}, - - {val: -2, ack: true, ts: nowSampleI23}, // 10s linear = 25 - {val: 7, ack: true, ts: nowSampleI23 + 20 * 1000}, - {val: 4, ack: true, ts: nowSampleI23 + 50 * 1000}, - - {val: 4, ack: true, ts: nowSampleI24 + 10 * 1000}, // 10s linear = 32.5 - {val: -3, ack: true, ts: nowSampleI24 + 20 * 1000}, - {val: 19, ack: true, ts: nowSampleI24 + 30 * 1000}, - {val: 1, ack: true, ts: nowSampleI24 + 50 * 1000}, - ] - }, function (result) { - expect(result.success).to.be.true; - - setTimeout(() => { - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: nowSampleI1, - end: nowSampleI1 + 30 * 60 * 1000, - count: 1, - aggregate: 'integral', - removeBorderValues: true, - integralUnit: 1, - integralInterpolation: 'none' - } - }, function (result) { - console.log(`Sample I1-1: ${JSON.stringify(result.result, null, 2)}`); - if (instanceName !== 'influxdb.0') { - expect(result.result.length).to.be.equal(1); - if (assumeExistingData) { - expect(result.result[0].val).to.be.within(3700, 3755); - } else { - expect(result.result[0].val).to.be.within(3700, 3800); - } - } else { - if (assumeExistingData) { - expect(result.result.length).to.be.within(2,3); - if (process.env.INFLUXDB2) { - //expect((result.result[0].val + result.result[1].val)).to.be.within(3780, 4000); - } else { - //expect(result.result[0].val).to.be.within(2980, 3000); - } - } else { - expect(result.result.length).to.be.equal(2); - if (process.env.INFLUXDB2) { - expect((result.result[0].val + result.result[1].val)).to.be.within(2980, 3000); - } else { - expect(parseFloat((result.result[0].val + result.result[1].val).toFixed(2))).to.be.equal(3732.66); - } - } - - } - // Result Influxdb1 Doku = 3732.66 - - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: nowSampleI1, - end: nowSampleI1 + 30 * 60 * 1000, - count: 1, - aggregate: 'integral', - removeBorderValues: true, - integralUnit: 60, - integralInterpolation: 'none' - } - }, function (result) { - console.log(`Sample I1-60: ${JSON.stringify(result.result, null, 2)}`); - if (instanceName !== 'influxdb.0') { - expect(result.result.length).to.be.equal(1); - if (assumeExistingData) { - expect(result.result[0].val).to.be.lessThan(62.25); - } else { - expect(result.result[0].val).to.be.equal(62.25); - } - } else { - if (assumeExistingData) { - expect(result.result.length).to.be.equal(3); - //expect(result.result[1].val).to.be.within(40, 65); - } else { - expect(result.result.length).to.be.equal(2); - if (process.env.INFLUXDB2) { - expect(parseFloat((result.result[0].val + result.result[1].val).toFixed(2))).to.be.within(49, 50); - } else { - expect(parseFloat((result.result[0].val + result.result[1].val).toFixed(2))).to.be.equal(62.21); - } - } - } - // Result Influxdb1 Doku = 62.211 - - sendTo(instanceName, 'getHistory', { + setTimeout(() => { + sendTo( + instanceName, + 'getHistory', + { id: `${instanceName}.testValue`, options: { - start: nowSampleI21, - end: nowSampleI21 + 60 * 1000, - count: 1, + start: nowSampleI1, + end: nowSampleI1 + 30 * 60 * 1000, + count: 1, aggregate: 'integral', removeBorderValues: true, - integralUnit: 10, - integralInterpolation: 'none' - } - }, function (result) { - console.log(`Sample I21: ${JSON.stringify(result.result, null, 2)}`); + integralUnit: 1, + integralInterpolation: 'none', + }, + }, + function (result) { + console.log(`Sample I1-1: ${JSON.stringify(result.result, null, 2)}`); if (instanceName !== 'influxdb.0') { expect(result.result.length).to.be.equal(1); - expect(result.result[0].val).to.be.equal(51); - } else { - expect(result.result.length).to.be.within(1, 2); - expect(result.result[0].val + (result.result[1] ? result.result[1].val : 0)).to.be.within(30, 50); - } - // Result Influxdb21 Doku = 50.0 - - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: nowSampleI22, - end: nowSampleI22 + 60 * 1000, - count: 1, - aggregate: 'integral', - removeBorderValues: true, - integralUnit: 10, - integralInterpolation: 'none' + if (assumeExistingData) { + expect(result.result[0].val).to.be.within(3700, 3755); + } else { + expect(result.result[0].val).to.be.within(3700, 3800); } - }, function (result) { - console.log(`Sample I22: ${JSON.stringify(result.result, null, 2)}`); - if (instanceName !== 'influxdb.0') { - expect(result.result.length).to.be.equal(1); - expect(result.result[0].val).to.be.equal(53); + } else { + if (assumeExistingData) { + expect(result.result.length).to.be.within(2, 3); + if (process.env.INFLUXDB2) { + //expect((result.result[0].val + result.result[1].val)).to.be.within(3780, 4000); + } else { + //expect(result.result[0].val).to.be.within(2980, 3000); + } } else { - expect(result.result.length).to.be.within(1,2); - expect(result.result[0].val + (result.result[1] ? result.result[1].val : 0)).to.be.within(27,43); + expect(result.result.length).to.be.equal(2); + if (process.env.INFLUXDB2) { + expect(result.result[0].val + result.result[1].val).to.be.within( + 2980, + 3000, + ); + } else { + expect( + parseFloat((result.result[0].val + result.result[1].val).toFixed(2)), + ).to.be.equal(3732.66); + } } - // Result Influxdb22 Doku = 43 + } + // Result Influxdb1 Doku = 3732.66 - sendTo(instanceName, 'getHistory', { + sendTo( + instanceName, + 'getHistory', + { id: `${instanceName}.testValue`, options: { - start: nowSampleI23, - end: nowSampleI23 + 60 * 1000, - count: 1, + start: nowSampleI1, + end: nowSampleI1 + 30 * 60 * 1000, + count: 1, aggregate: 'integral', removeBorderValues: true, - integralUnit: 10, - integralInterpolation: 'linear' - } - }, function (result) { - console.log(`Sample I23: ${JSON.stringify(result.result, null, 2)}`); + integralUnit: 60, + integralInterpolation: 'none', + }, + }, + function (result) { + console.log(`Sample I1-60: ${JSON.stringify(result.result, null, 2)}`); if (instanceName !== 'influxdb.0') { expect(result.result.length).to.be.equal(1); - expect(result.result[0].val).to.be.equal(25.5); - } else { - expect(result.result.length).to.be.within(1,2); - if (process.env.INFLUXDB2) { - //expect(result.result[0].val).to.be.equal(25.5); + if (assumeExistingData) { + expect(result.result[0].val).to.be.lessThan(62.25); } else { - expect(result.result[0].val).to.be.equal(34.5); + expect(result.result[0].val).to.be.equal(62.25); } - } - // Result Influxdb23 Doku = 25.0 - - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: nowSampleI24, - end: nowSampleI24 + 60 * 1000, - count: 1, - aggregate: 'integral', - removeBorderValues: true, - integralUnit: 10, - integralInterpolation: 'linear' - } - }, function (result) { - console.log(`Sample I24: ${JSON.stringify(result.result, null, 2)}`); - if (instanceName !== 'influxdb.0') { - expect(result.result.length).to.be.equal(1); - if (assumeExistingData) { - expect(result.result[0].val).to.be.within(31, 32); - } else { - expect(result.result[0].val).to.be.within(32, 33.5); - } + } else { + if (assumeExistingData) { + expect(result.result.length).to.be.equal(3); + //expect(result.result[1].val).to.be.within(40, 65); } else { - expect(result.result.length).to.be.within(1,2); + expect(result.result.length).to.be.equal(2); if (process.env.INFLUXDB2) { - //expect(result.result[0].val).to.be.equal(25.5); + expect( + parseFloat( + (result.result[0].val + result.result[1].val).toFixed(2), + ), + ).to.be.within(49, 50); } else { - if (assumeExistingData) { - expect(result.result[0].val).to.be.within(31, 34); - } else { - expect(result.result[0].val).to.be.within(32, 33.5); - } + expect( + parseFloat( + (result.result[0].val + result.result[1].val).toFixed(2), + ), + ).to.be.equal(62.21); } } - // Result Influxdb24 Doku = 32.5 + } + // Result Influxdb1 Doku = 62.211 - sendTo(instanceName, 'getHistory', { + sendTo( + instanceName, + 'getHistory', + { id: `${instanceName}.testValue`, options: { - start: nowSampleI22, - end: nowSampleI22 + 60 * 1000, - count: 1, - aggregate: 'quantile', - quantile: 0.8 - } - }, function (result) { - console.log(`Sample I22-Quantile: ${JSON.stringify(result.result, null, 2)}`); + start: nowSampleI21, + end: nowSampleI21 + 60 * 1000, + count: 1, + aggregate: 'integral', + removeBorderValues: true, + integralUnit: 10, + integralInterpolation: 'none', + }, + }, + function (result) { + console.log(`Sample I21: ${JSON.stringify(result.result, null, 2)}`); if (instanceName !== 'influxdb.0') { - expect(result.result.length).to.be.equal(3); - expect(result.result[1].val).to.be.equal(19); + expect(result.result.length).to.be.equal(1); + expect(result.result[0].val).to.be.equal(51); } else { - expect(result.result.length).to.be.within(3, 4); - expect(result.result[1].val).to.be.within(4, 19); + expect(result.result.length).to.be.within(1, 2); + expect( + result.result[0].val + + (result.result[1] ? result.result[1].val : 0), + ).to.be.within(30, 50); } - - resolve(); - }); - }); - }); - }); - }); - }); - }); - }, 1000); - }); + // Result Influxdb21 Doku = 50.0 + + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: nowSampleI22, + end: nowSampleI22 + 60 * 1000, + count: 1, + aggregate: 'integral', + removeBorderValues: true, + integralUnit: 10, + integralInterpolation: 'none', + }, + }, + function (result) { + console.log( + `Sample I22: ${JSON.stringify(result.result, null, 2)}`, + ); + if (instanceName !== 'influxdb.0') { + expect(result.result.length).to.be.equal(1); + expect(result.result[0].val).to.be.equal(53); + } else { + expect(result.result.length).to.be.within(1, 2); + expect( + result.result[0].val + + (result.result[1] ? result.result[1].val : 0), + ).to.be.within(27, 43); + } + // Result Influxdb22 Doku = 43 + + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: nowSampleI23, + end: nowSampleI23 + 60 * 1000, + count: 1, + aggregate: 'integral', + removeBorderValues: true, + integralUnit: 10, + integralInterpolation: 'linear', + }, + }, + function (result) { + console.log( + `Sample I23: ${JSON.stringify(result.result, null, 2)}`, + ); + if (instanceName !== 'influxdb.0') { + expect(result.result.length).to.be.equal(1); + expect(result.result[0].val).to.be.equal(25.5); + } else { + expect(result.result.length).to.be.within(1, 2); + if (process.env.INFLUXDB2) { + //expect(result.result[0].val).to.be.equal(25.5); + } else { + expect(result.result[0].val).to.be.equal(34.5); + } + } + // Result Influxdb23 Doku = 25.0 + + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: nowSampleI24, + end: nowSampleI24 + 60 * 1000, + count: 1, + aggregate: 'integral', + removeBorderValues: true, + integralUnit: 10, + integralInterpolation: 'linear', + }, + }, + function (result) { + console.log( + `Sample I24: ${JSON.stringify(result.result, null, 2)}`, + ); + if (instanceName !== 'influxdb.0') { + expect(result.result.length).to.be.equal(1); + if (assumeExistingData) { + expect( + result.result[0].val, + ).to.be.within(31, 32); + } else { + expect( + result.result[0].val, + ).to.be.within(32, 33.5); + } + } else { + expect(result.result.length).to.be.within( + 1, + 2, + ); + if (process.env.INFLUXDB2) { + //expect(result.result[0].val).to.be.equal(25.5); + } else { + if (assumeExistingData) { + expect( + result.result[0].val, + ).to.be.within(31, 34); + } else { + expect( + result.result[0].val, + ).to.be.within(32, 33.5); + } + } + } + // Result Influxdb24 Doku = 32.5 + + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: nowSampleI22, + end: nowSampleI22 + 60 * 1000, + count: 1, + aggregate: 'quantile', + quantile: 0.8, + }, + }, + function (result) { + console.log( + `Sample I22-Quantile: ${JSON.stringify(result.result, null, 2)}`, + ); + if (instanceName !== 'influxdb.0') { + expect( + result.result.length, + ).to.be.equal(3); + expect( + result.result[1].val, + ).to.be.equal(19); + } else { + expect( + result.result.length, + ).to.be.within(3, 4); + expect( + result.result[1].val, + ).to.be.within(4, 19); + } + + resolve(); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + }, 1000); + }, + ); }); }); @@ -912,86 +1104,111 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti const start1week = Date.now() - 7 * 24 * 60 * 60 * 1000; - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: start1week, - end: start1week + 7 * 24 * 60 * 60 * 1000, - step: 24 * 60 * 60 * 1000, - aggregate: 'integral', - integralUnit: 3600, - addId: true - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.equal(4); - expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); - done(); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: start1week, + end: start1week + 7 * 24 * 60 * 60 * 1000, + step: 24 * 60 * 60 * 1000, + aggregate: 'integral', + integralUnit: 3600, + addId: true, + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.equal(4); + expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); + done(); + }, + ); }); it(`Test ${adapterShortName}: Remove Alias-ID`, function (done) { this.timeout(5000); - sendTo(instanceName, 'enableHistory', { - id: `${instanceName}.testValue2`, - options: { - aliasId: '' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 2000); - }); + sendTo( + instanceName, + 'enableHistory', + { + id: `${instanceName}.testValue2`, + options: { + aliasId: '', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 2000); + }, + ); }); it(`Test ${adapterShortName}: Add Alias-ID again`, function (done) { this.timeout(5000); - sendTo(instanceName, 'enableHistory', { - id: `${instanceName}.testValue2`, - options: { - aliasId: 'this.is.a.test-value' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 2000); - }); + sendTo( + instanceName, + 'enableHistory', + { + id: `${instanceName}.testValue2`, + options: { + aliasId: 'this.is.a.test-value', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 2000); + }, + ); }); it(`Test ${adapterShortName}: Change Alias-ID`, function (done) { this.timeout(5000); - sendTo(instanceName, 'enableHistory', { - id: `${instanceName}.testValue2`, - options: { - aliasId: 'this.is.another.test-value' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 2000); - }); + sendTo( + instanceName, + 'enableHistory', + { + id: `${instanceName}.testValue2`, + options: { + aliasId: 'this.is.another.test-value', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 2000); + }, + ); }); it(`Test ${adapterShortName}: Disable Datapoint again`, function (done) { this.timeout(5000); - sendTo(instanceName, 'disableHistory', { - id: `${instanceName}.testValue` - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - setTimeout(done, 2000); - }); + sendTo( + instanceName, + 'disableHistory', + { + id: `${instanceName}.testValue`, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + setTimeout(done, 2000); + }, + ); }); it(`Test ${adapterShortName}: Check Enabled Points after Disable`, function (done) { this.timeout(5000); @@ -1006,135 +1223,174 @@ function register(it, expect, sendTo, adapterShortName, writeNulls, assumeExisti it(`Test ${adapterShortName}: Enable testValue Datapoint again`, function (done) { this.timeout(5000); - sendTo(instanceName, 'enableHistory', { - id: `${instanceName}.testValue` - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - setTimeout(done, 2000); - }); + sendTo( + instanceName, + 'enableHistory', + { + id: `${instanceName}.testValue`, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + setTimeout(done, 2000); + }, + ); }); it(`Test ${adapterShortName}: Check for written Null values`, function (done) { this.timeout(25000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: preInitTime, - count: 500, - aggregate: 'none' - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.at.least(5); - var found = 0; - for (var i = 0; i < result.result.length; i++) { - if (result.result[i].val === null) found++; - } - if (writeNulls) { - expect(found).to.be.equal(3); - } else { - expect(found).to.be.equal(0); - } + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: preInitTime, + count: 500, + aggregate: 'none', + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.at.least(5); + let found = 0; + for (let i = 0; i < result.result.length; i++) { + if (result.result[i].val === null) found++; + } + if (writeNulls) { + expect(found).to.be.equal(3); + } else { + expect(found).to.be.equal(0); + } - done(); - }); + done(); + }, + ); }); it(`Test ${adapterShortName}: Check for written Data in general`, function (done) { this.timeout(25000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - count: 500, - aggregate: 'none' - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.at.least((writeNulls? 3 : 0) + ((assumeExistingData + 1) * 30)); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + count: 500, + aggregate: 'none', + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.at.least((writeNulls ? 3 : 0) + (assumeExistingData + 1) * 30); - done(); - }); + done(); + }, + ); }); it(`Test ${adapterShortName}: Read minmax values from DB using GetHistory with 1mio slices`, function (done) { this.timeout(20000); - sendTo(instanceName, 'getHistory', { - id: `${instanceName}.testValue`, - options: { - start: Date.now() - 7 * 24 * 60 * 60 * 1000, - end: Date.now(), - count: 1000000, - limit: 1000000, - aggregate: 'minmax', - addId: true - } - }, result => { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.at.least(4); - expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); - done(); - }); + sendTo( + instanceName, + 'getHistory', + { + id: `${instanceName}.testValue`, + options: { + start: Date.now() - 7 * 24 * 60 * 60 * 1000, + end: Date.now(), + count: 1000000, + limit: 1000000, + aggregate: 'minmax', + addId: true, + }, + }, + result => { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.at.least(4); + expect(result.result[0].id).to.be.equal(`${instanceName}.testValue`); + done(); + }, + ); }); it(`Test ${adapterShortName}: storeState and getHistory for unknown Id`, function (done) { this.timeout(25000); const customNow = Date.now(); - sendTo(instanceName, 'storeState', { - id: `my.own.unknown.value-${customNow}`, - state: [ - {val: 1, ack: true, ts: customNow - 5000}, - {val: 2, ack: true, ts: customNow - 4000}, - {val: 3, ack: true, ts: customNow - 3000} - ] - }, function (result) { - expect(result.success).to.be.true; - expect(result.successCount).to.be.equal(3); - - setTimeout( () => { - sendTo(instanceName, 'getHistory', { - id: `my.own.unknown.value-${customNow}`, - options: { - start: customNow - 10000, - count: 500, - aggregate: 'none' - } - }, function (result) { - console.log(JSON.stringify(result.result, null, 2)); - expect(result.result.length).to.be.equal(3); + sendTo( + instanceName, + 'storeState', + { + id: `my.own.unknown.value-${customNow}`, + state: [ + { val: 1, ack: true, ts: customNow - 5000 }, + { val: 2, ack: true, ts: customNow - 4000 }, + { val: 3, ack: true, ts: customNow - 3000 }, + ], + }, + function (result) { + expect(result.success).to.be.true; + expect(result.successCount).to.be.equal(3); - done(); - }); - }, 1000); - }); + setTimeout(() => { + sendTo( + instanceName, + 'getHistory', + { + id: `my.own.unknown.value-${customNow}`, + options: { + start: customNow - 10000, + count: 500, + aggregate: 'none', + }, + }, + function (result) { + console.log(JSON.stringify(result.result, null, 2)); + expect(result.result.length).to.be.equal(3); + + done(); + }, + ); + }, 1000); + }, + ); }); it(`Test ${adapterShortName}: storeState error for unknown Id with rules parameter`, function (done) { this.timeout(25000); const customNow2 = Date.now(); - sendTo(instanceName, 'storeState', { - id: `my.own.unknown.value-${customNow2}`, - rules: true, - state: [ - {val: 1, ack: true, ts: customNow2 - 5000}, - {val: 2, ack: true, ts: customNow2 - 4000}, - '37' - ] - }, function (result) { - expect(result.success).to.be.not.ok; - expect(result.successCount).to.be.equal(0); - expect(result.error).to.be.equal('3 errors happened while storing data'); - expect(Array.isArray(result.errors)).to.be.true; - expect(result.errors[0].endsWith(` not enabled for my.own.unknown.value-${customNow2}, so can not apply the rules as requested`)).to.be.true; - expect(result.errors[2]).to.be.equal(`State "37" for my.own.unknown.value-${customNow2} is not valid`); + sendTo( + instanceName, + 'storeState', + { + id: `my.own.unknown.value-${customNow2}`, + rules: true, + state: [ + { val: 1, ack: true, ts: customNow2 - 5000 }, + { val: 2, ack: true, ts: customNow2 - 4000 }, + '37', + ], + }, + function (result) { + expect(result.success).to.be.not.ok; + expect(result.successCount).to.be.equal(0); + expect(result.error).to.be.equal('3 errors happened while storing data'); + expect(Array.isArray(result.errors)).to.be.true; + expect( + result.errors[0].endsWith( + ` not enabled for my.own.unknown.value-${customNow2}, so can not apply the rules as requested`, + ), + ).to.be.true; + expect(result.errors[2]).to.be.equal(`State "37" for my.own.unknown.value-${customNow2} is not valid`); - done(); - }); + done(); + }, + ); }); } diff --git a/test/mocha.setup.js b/test/mocha.setup.js index 2adcb98e..f6410403 100644 --- a/test/mocha.setup.js +++ b/test/mocha.setup.js @@ -1 +1,3 @@ -process.on("unhandledRejection", (r) => { throw r; }); +process.on('unhandledRejection', r => { + throw r; +}); diff --git a/test/testCommons.js b/test/testCommons.js index 296ff980..0110f58d 100644 --- a/test/testCommons.js +++ b/test/testCommons.js @@ -1,40 +1,40 @@ const expect = require('chai').expect; -const commons = require('../lib/aggregate'); +const commons = require('../build/lib/aggregate'); describe('Test Common functions', function () { const log = { - info: t => console.log(t), + info: t => console.log(t), debug: t => console.log(t), error: t => console.error(t), - warn: t => console.warn(t), + warn: t => console.warn(t), }; - + it('Test Common functions: counter 1', function (done) { const timeSeries = [ - {ts: 0, val: 100}, - {ts: 10, val: 200}, - {ts: 40, val: 500}, - {ts: 50, val: 0}, - {ts: 90, val: 400}, - {ts: 100, val: 0}, - {ts: 110, val: 100}, + { ts: 0, val: 100 }, + { ts: 10, val: 200 }, + { ts: 40, val: 500 }, + { ts: 50, val: 0 }, + { ts: 90, val: 400 }, + { ts: 100, val: 0 }, + { ts: 110, val: 100 }, ]; - + const adapter = { sendTo: function (from, command, result, callback) { expect(result.result).to.be.equal(700); done(); }, log, - } + }; - commons.sendResponseCounter(adapter, {}, {start: 10, end: 100}, timeSeries); + commons.sendResponseCounter(adapter, {}, { start: 10, end: 100 }, timeSeries); }); it('Test Common functions: counter 2', function (done) { const timeSeries = [ - {ts: 10, val: 200}, - {ts: 40, val: 500}, + { ts: 10, val: 200 }, + { ts: 40, val: 500 }, ]; const adapter = { @@ -43,18 +43,18 @@ describe('Test Common functions', function () { done(); }, log, - } + }; - commons.sendResponseCounter(adapter, {}, {start: 10, end: 40}, timeSeries); + commons.sendResponseCounter(adapter, {}, { start: 10, end: 40 }, timeSeries); }); it('Test Common functions: counter 3', function (done) { const timeSeries = [ - {ts: 0, val: 100}, - {ts: 10, val: 200}, - {ts: 40, val: 500}, - {ts: 50, val: 0}, - {ts: 90, val: 400}, + { ts: 0, val: 100 }, + { ts: 10, val: 200 }, + { ts: 40, val: 500 }, + { ts: 50, val: 0 }, + { ts: 90, val: 400 }, ]; const adapter = { @@ -63,20 +63,20 @@ describe('Test Common functions', function () { done(); }, log, - } + }; - commons.sendResponseCounter(adapter, {}, {start: 5, end: 70}, timeSeries); + commons.sendResponseCounter(adapter, {}, { start: 5, end: 70 }, timeSeries); }); it('Test Common functions: counter 4', function (done) { const timeSeries = [ - {ts: 0, val: 100}, - {ts: 10, val: 200}, - {ts: 40, val: 500}, - {ts: 50, val: 0}, - {ts: 90, val: 400}, - {ts: 100, val: 0}, - {ts: 110, val: 100}, + { ts: 0, val: 100 }, + { ts: 10, val: 200 }, + { ts: 40, val: 500 }, + { ts: 50, val: 0 }, + { ts: 90, val: 400 }, + { ts: 100, val: 0 }, + { ts: 110, val: 100 }, ]; const adapter = { @@ -85,19 +85,19 @@ describe('Test Common functions', function () { done(); }, log, - } + }; - commons.sendResponseCounter(adapter, {}, {start: 5, end: 105}, timeSeries); + commons.sendResponseCounter(adapter, {}, { start: 5, end: 105 }, timeSeries); }); it('Test Common functions: counter 5', function (done) { const timeSeries = [ - {ts: 0, val: 100}, - {ts: 10, val: 200}, - {ts: 40, val: 500}, - {ts: 50, val: 0}, - {ts: 90, val: 400}, - {ts: 100, val: 0}, + { ts: 0, val: 100 }, + { ts: 10, val: 200 }, + { ts: 40, val: 500 }, + { ts: 50, val: 0 }, + { ts: 90, val: 400 }, + { ts: 100, val: 0 }, ]; const adapter = { @@ -106,20 +106,20 @@ describe('Test Common functions', function () { done(); }, log, - } + }; - commons.sendResponseCounter(adapter, {}, {start: 5, end: 95}, timeSeries); + commons.sendResponseCounter(adapter, {}, { start: 5, end: 95 }, timeSeries); }); it('Test Common functions: counter 6', function (done) { const timeSeries = [ - {ts: 0, val: 100}, - {ts: 10, val: 0}, - {ts: 20, val: 100}, - {ts: 40, val: 500}, - {ts: 50, val: 0}, - {ts: 90, val: 400}, - {ts: 100, val: 0}, + { ts: 0, val: 100 }, + { ts: 10, val: 0 }, + { ts: 20, val: 100 }, + { ts: 40, val: 500 }, + { ts: 50, val: 0 }, + { ts: 90, val: 400 }, + { ts: 100, val: 0 }, ]; const adapter = { @@ -128,9 +128,8 @@ describe('Test Common functions', function () { done(); }, log, - } + }; - commons.sendResponseCounter(adapter, {}, {start: 5, end: 95}, timeSeries); + commons.sendResponseCounter(adapter, {}, { start: 5, end: 95 }, timeSeries); }); }); - diff --git a/test/testIntegral.js b/test/testIntegral.js new file mode 100644 index 00000000..ea5bfd04 --- /dev/null +++ b/test/testIntegral.js @@ -0,0 +1,141 @@ +/* eslint-env mocha */ +const { expect } = require('chai'); + +// Pfad ggf. anpassen: Modul muss finishAggregationForIntegralEx exportieren +const { finishAggregationForIntegralEx } = require('../build/lib/aggregate'); + +function makeIntervals(start, step, count) { + const arr = []; + for (let i = 0; i < count; i++) { + arr.push({ start: start + i * step, end: start + (i + 1) * step }); + } + return arr; +} + +describe('finishAggregationForIntegralEx', () => { + it('returns zeros when no pre‑interval data is available', () => { + const intervals = makeIntervals(0, 1000, 2); // [0..1000), [1000..2000) + const options = { + timeIntervals: intervals, + // integralDataPoints muss Länge = intervals.length + 2 haben (pre + buckets + post) + integralDataPoints: [[], [], [], []], + logDebug: false, + }; + + finishAggregationForIntegralEx(options); + + expect(options.result).to.have.lengthOf(2); + // Check midpoints + expect(options.result[0].ts).to.equal(0 + Math.round((1000 - 0) / 2)); // 500 + expect(options.result[1].ts).to.equal(1000 + Math.round((2000 - 1000) / 2)); // 1500 + // Werte bleiben 0 + expect(options.result[0].val).to.equal(0); + expect(options.result[1].val).to.equal(0); + }); + + it('fills start/end points via interpolation and yields positive integrals (increasing trend)', () => { + const intervals = makeIntervals(0, 1000, 2); + const buckets = [ + // pre + [{ ts: -1, val: 0 }], + // interval 0 + [], + // interval 1 + [], + // post + [{ ts: 2000, val: 10 }], + ]; + const options = { + timeIntervals: intervals, + integralDataPoints: buckets, + logDebug: false, + }; + + finishAggregationForIntegralEx(options); + + expect(options.result).to.have.lengthOf(2); + // Buckets wurden mit Start (start) und Ende (end-1) befüllt + for (let i = 0; i < intervals.length; i++) { + const b = options.integralDataPoints[i + 1]; + expect(b).to.be.an('array').that.is.not.empty; + expect(b[0].ts).to.equal(intervals[i].start); + expect(b[b.length - 1].ts).to.equal(intervals[i].end - 1); + } + // Midpoints + expect(options.result[0].ts).to.equal(500); + expect(options.result[1].ts).to.equal(1500); + + // Steigender Verlauf => Integrale > 0 und zweites Intervall größer als erstes + expect(options.result[0].val).to.be.a('number'); + expect(options.result[1].val).to.be.a('number'); + expect(options.result[0].val).to.be.greaterThan(0); + expect(options.result[1].val).to.be.greaterThan(options.result[0].val); + }); + + it('yields equal integrals for a constant value across all intervals', () => { + const intervals = makeIntervals(0, 1000, 2); + const buckets = [ + // pre: konstanter Wert 5 vor Start + [{ ts: -1, val: 5 }], + // interval 0 + [], + // interval 1 + [], + // post: gleicher Wert nach Ende + [{ ts: 2000, val: 5 }], + ]; + const options = { + timeIntervals: intervals, + integralDataPoints: buckets, + logDebug: false, + }; + + finishAggregationForIntegralEx(options); + + expect(options.result).to.have.lengthOf(2); + // Beide Intervalle sollten das gleiche Integral liefern (konstante Funktion) + expect(options.result[0].val).to.be.a('number'); + expect(options.result[1].val).to.be.a('number'); + expect(Math.abs(options.result[0].val - options.result[1].val)).to.be.below(1e-9); + }); + + it('computes exact integrals for a known linear function f(t) = 2t + 3', () => { + const intervals = makeIntervals(0, 1000, 2); + const f = t => 2 * t + 3; + + // Dataset: for each bucket provide only start and (end - 1) points + const buckets = [ + [], // pre + [ + { ts: intervals[0].start, val: f(intervals[0].start) }, // f(0) = 3 + { ts: intervals[0].end - 1, val: f(intervals[0].end - 1) }, // f(999) = 2001 + ], + [ + { ts: intervals[1].start, val: f(intervals[1].start) }, // f(1000) = 2003 + { ts: intervals[1].end - 1, val: f(intervals[1].end - 1) }, // f(1999) = 4001 + ], + [], // post + ]; + + const options = { + timeIntervals: intervals, + integralDataPoints: buckets, + logDebug: false, + }; + + // Exact integrals via trapezoidal rule over [start, end - 1] + const expected = intervals.map(iv => { + const S = iv.start; + const E1 = iv.end - 1; + return 0.5 * (f(S) + f(E1)) * (E1 - S) / 3_600_000; // in hours + }); + // Expected: [0.278055, 0.833055] + + finishAggregationForIntegralEx(options); + + expect(options.result).to.have.lengthOf(2); + expect(Math.abs(options.result[0].val - expected[0])).to.be.lessThan(0.00000001); // 0.278055 + expect(Math.abs(options.result[1].val - expected[1])).to.be.lessThan(0.00000001); // 0.833055 + }); + +}); diff --git a/test/testMSSQL.js b/test/testMSSQL.js index bfa9e4ad..c9600149 100644 --- a/test/testMSSQL.js +++ b/test/testMSSQL.js @@ -1,4 +1,4 @@ -/* jshint -W097 */// jshint strict:false +/* jshint -W097 */ // jshint strict:false /*jslint node: true */ /*jshint expr: true*/ const expect = require('chai').expect; @@ -8,7 +8,6 @@ const tests = require('./lib/testcases'); let objects = null; let states = null; let onStateChanged = null; -let onObjectChanged = null; let sendToID = 1; const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1); @@ -16,42 +15,20 @@ const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf(' let now = new Date().getTime(); function checkConnectionOfAdapter(cb, counter) { - counter = counter || 0; + counter ||= 0; if (counter > 20) { - cb && cb('Cannot check connection'); + cb?.('Cannot check connection'); return; } - states.getState(`system.adapter.${adapterShortName}.0.alive`, function (err, state) { - if (err) console.error(`MSSQL: ${err}`); - if (state && state.val) { - cb && cb(); - } else { - setTimeout(function () { - checkConnectionOfAdapter(cb, counter + 1); - }, 1000); + states.getState(`system.adapter.${adapterShortName}.0.alive`, (err, state) => { + if (err) { + console.error(`SQLite:${err}`); } - }); -} - -function checkValueOfState(id, value, cb, counter) { - counter = counter || 0; - if (counter > 20) { - cb && cb(`Cannot check value Of State ${id}`); - return; - } - - states.getState(id, function (err, state) { - if (err) console.error(`MSSQL: ${err}`); - if (value === null && !state) { - cb && cb(); - } else - if (state && (value === undefined || state.val === value)) { - cb && cb(); + if (state?.val) { + cb?.(); } else { - setTimeout(function () { - checkValueOfState(id, value, cb, counter + 1); - }, 500); + setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); } }); } @@ -64,48 +41,52 @@ function sendTo(target, command, message, callback) { }; states.pushMessage(`system.adapter.${target}`, { - command: command, - message: message, - from: 'system.adapter.test.0', + command: command, + message: message, + from: 'system.adapter.test.0', callback: { message: message, - id: sendToID++, - ack: false, - time: (new Date()).getTime() - } + id: sendToID++, + ack: false, + time: new Date().getTime(), + }, }); } -describe(`Test ${__filename}`, function() { +describe(`Test ${__filename}`, function () { before(`Test ${__filename}: Start js-controller`, function (_done) { this.timeout(600000); // because of first install from npm setup.adapterStarted = false; setup.setupController(async function () { - var config = await setup.getAdapterConfig(); + const config = await setup.getAdapterConfig(); // enable adapter - config.common.enabled = true; + config.common.enabled = true; config.common.loglevel = 'debug'; config.native.enableDebugLogs = true; config.native.host = '127.0.0.1'; - config.native.dbtype = 'mssql'; - config.native.user = 'sa'; + config.native.dbtype = 'mssql'; + config.native.user = 'sa'; config.native.password = 'Password12!'; await setup.setAdapterConfig(config.common, config.native); - setup.startController(true, function(id, obj) {}, function (id, state) { + setup.startController( + true, + function (id, obj) {}, + function (id, state) { if (onStateChanged) onStateChanged(id, state); }, async (_objects, _states) => { objects = _objects; - states = _states; + states = _states; await tests.preInit(objects, states, sendTo, adapterShortName); _done(); - }); + }, + ); }); }); @@ -113,34 +94,44 @@ describe(`Test ${__filename}`, function() { this.timeout(60000); checkConnectionOfAdapter(function () { now = new Date().getTime(); - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.memHeapTotal', - options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'String' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.uptime', + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.memHeapTotal', options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'Boolean' - } - }, function (result) { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'String', + }, + }, + function (result) { expect(result.error).to.be.undefined; expect(result.success).to.be.true; - // wait till the adapter receives the new settings - setTimeout(function () { - done(); - }, 10000); - }); - }); + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.uptime', + options: { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'Boolean', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till the adapter receives the new settings + setTimeout(function () { + done(); + }, 10000); + }, + ); + }, + ); }); }); @@ -149,10 +140,10 @@ describe(`Test ${__filename}`, function() { it(`Test ${__filename}: Check Datapoint Types`, function (done) { this.timeout(5000); - sendTo('sql.0', 'query', "SELECT name, type FROM iobroker.dbo.datapoints", function (result) { + sendTo('sql.0', 'query', 'SELECT name, type FROM iobroker.dbo.datapoints', function (result) { console.log(`MSSQL: ${JSON.stringify(result.result, null, 2)}`); expect(result.result.length).to.least(3); - for (var i = 0; i < result.result.length; i++) { + for (let i = 0; i < result.result.length; i++) { if (result.result[i].name === 'sql.0.testValue') { expect(result.result[i].type).to.be.equal(0); } else if (result.result[i].name === 'sql.0.testValueDebounce') { diff --git a/test/testMySQL.js b/test/testMySQL.js index 764cf9e4..7e9fa1f0 100644 --- a/test/testMySQL.js +++ b/test/testMySQL.js @@ -1,4 +1,4 @@ -/* jshint -W097 */// jshint strict:false +/* jshint -W097 */ // jshint strict:false /*jslint node: true */ /*jshint expr: true*/ const expect = require('chai').expect; @@ -8,7 +8,6 @@ const tests = require('./lib/testcases'); let objects = null; let states = null; let onStateChanged = null; -let onObjectChanged = null; let sendToID = 1; const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1); @@ -16,42 +15,20 @@ const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf(' let now = new Date().getTime(); function checkConnectionOfAdapter(cb, counter) { - counter = counter || 0; + counter ||= 0; if (counter > 20) { - cb && cb('Cannot check connection'); + cb?.('Cannot check connection'); return; } - states.getState(`system.adapter.${adapterShortName}.0.alive`, function (err, state) { - if (err) console.error(`MySQL: ${err}`); - if (state && state.val) { - cb && cb(); - } else { - setTimeout(function () { - checkConnectionOfAdapter(cb, counter + 1); - }, 1000); + states.getState(`system.adapter.${adapterShortName}.0.alive`, (err, state) => { + if (err) { + console.error(`SQLite:${err}`); } - }); -} - -function checkValueOfState(id, value, cb, counter) { - counter = counter || 0; - if (counter > 20) { - cb && cb(`Cannot check value Of State ${id}`); - return; - } - - states.getState(id, function (err, state) { - if (err) console.error(`MySQL: ${err}`); - if (value === null && !state) { - cb && cb(); - } else - if (state && (value === undefined || state.val === value)) { - cb && cb(); + if (state?.val) { + cb?.(); } else { - setTimeout(function () { - checkValueOfState(id, value, cb, counter + 1); - }, 500); + setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); } }); } @@ -64,47 +41,51 @@ function sendTo(target, command, message, callback) { }; states.pushMessage(`system.adapter.${target}`, { - command: command, - message: message, - from: 'system.adapter.test.0', + command: command, + message: message, + from: 'system.adapter.test.0', callback: { message: message, - id: sendToID++, - ack: false, - time: (new Date()).getTime() - } + id: sendToID++, + ack: false, + time: new Date().getTime(), + }, }); } -describe(`Test ${__filename}`, function() { +describe(`Test ${__filename}`, function () { before(`Test ${__filename}: Start js-controller`, function (_done) { this.timeout(600000); // because of first install from npm setup.adapterStarted = false; setup.setupController(async function () { - var config = await setup.getAdapterConfig(); + const config = await setup.getAdapterConfig(); // enable adapter - config.common.enabled = true; + config.common.enabled = true; config.common.loglevel = 'debug'; config.native.maxConnections = 1; config.native.enableDebugLogs = true; config.native.host = '127.0.0.1'; - config.native.dbtype = 'mysql'; - config.native.user = 'root'; + config.native.dbtype = 'mysql'; + config.native.user = 'root'; config.native.password = process.env.SQL_PASS || ''; await setup.setAdapterConfig(config.common, config.native); - setup.startController(true, (id, obj) => {}, (id, state) => onStateChanged && onStateChanged(id, state), + setup.startController( + true, + (id, obj) => {}, + (id, state) => onStateChanged && onStateChanged(id, state), async (_objects, _states) => { objects = _objects; - states = _states; + states = _states; await tests.preInit(objects, states, sendTo, adapterShortName); _done(); - }); + }, + ); }); }); @@ -112,34 +93,44 @@ describe(`Test ${__filename}`, function() { this.timeout(60000); checkConnectionOfAdapter(function () { now = new Date().getTime(); - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.memHeapTotal', - options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'String' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.uptime', + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.memHeapTotal', options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'Boolean' - } - }, function (result) { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'String', + }, + }, + function (result) { expect(result.error).to.be.undefined; expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 10000); - }); - }); + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.uptime', + options: { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'Boolean', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 10000); + }, + ); + }, + ); }); }); @@ -148,10 +139,10 @@ describe(`Test ${__filename}`, function() { it(`Test ${__filename}: Check Datapoint Types`, function (done) { this.timeout(5000); - sendTo('sql.0', 'query', "SELECT id, name, type FROM iobroker.datapoints", function (result) { + sendTo('sql.0', 'query', 'SELECT id, name, type FROM iobroker.datapoints', function (result) { console.log(`MySQL: ${JSON.stringify(result.result, null, 2)}`); expect(result.result.length).to.least(3); - for (var i = 0; i < result.result.length; i++) { + for (let i = 0; i < result.result.length; i++) { if (result.result[i].name === 'sql.0.testValue') { expect(result.result[i].type).to.be.equal(0); } else if (result.result[i].name === 'sql.0.testValueDebounce') { diff --git a/test/testMySQLDash.js b/test/testMySQLDash.js index 19ef44da..28cd47b4 100644 --- a/test/testMySQLDash.js +++ b/test/testMySQLDash.js @@ -1,4 +1,4 @@ -/* jshint -W097 */// jshint strict:false +/* jshint -W097 */ // jshint strict:false /*jslint node: true */ /*jshint expr: true*/ const expect = require('chai').expect; @@ -6,9 +6,8 @@ const setup = require('./lib/setup'); const tests = require('./lib/testcases'); let objects = null; -let states = null; +let states = null; let onStateChanged = null; -let onObjectChanged = null; let sendToID = 1; const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1); @@ -16,42 +15,20 @@ const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf(' let now = new Date().getTime(); function checkConnectionOfAdapter(cb, counter) { - counter = counter || 0; + counter ||= 0; if (counter > 20) { - cb && cb('Cannot check connection'); + cb?.('Cannot check connection'); return; } states.getState(`system.adapter.${adapterShortName}.0.alive`, (err, state) => { - err && console.error(`MySQL-with-dash: ${err}`); - if (state && state.val) { - cb && cb(); - } else { - setTimeout(() => - checkConnectionOfAdapter(cb, counter + 1) - , 1000); + if (err) { + console.error(`SQLite:${err}`); } - }); -} - -function checkValueOfState(id, value, cb, counter) { - counter = counter || 0; - if (counter > 20) { - cb && cb(`Cannot check value Of State ${id}`); - return; - } - - states.getState(id, function (err, state) { - if (err) console.error(`MySQL-with-dash: ${err}`); - if (value === null && !state) { - cb && cb(); - } else - if (state && (value === undefined || state.val === value)) { - cb && cb(); + if (state?.val) { + cb?.(); } else { - setTimeout(function () { - checkValueOfState(id, value, cb, counter + 1); - }, 500); + setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); } }); } @@ -64,49 +41,53 @@ function sendTo(target, command, message, callback) { }; states.pushMessage(`system.adapter.${target}`, { - command: command, - message: message, - from: 'system.adapter.test.0', + command: command, + message: message, + from: 'system.adapter.test.0', callback: { message: message, - id: sendToID++, - ack: false, - time: (new Date()).getTime() - } + id: sendToID++, + ack: false, + time: new Date().getTime(), + }, }); } -describe(`Test ${__filename}`, function() { +describe(`Test ${__filename}`, function () { before(`Test ${__filename} Start js-controller`, function (_done) { this.timeout(600000); // because of first install from npm setup.adapterStarted = false; setup.setupController(async function () { - var config = await setup.getAdapterConfig(); + const config = await setup.getAdapterConfig(); // enable adapter - config.common.enabled = true; + config.common.enabled = true; config.common.loglevel = 'debug'; config.native.enableDebugLogs = true; config.native.host = '127.0.0.1'; - config.native.dbtype = 'mysql'; - config.native.user = 'root'; - config.native.dbname = 'io-broker'; + config.native.dbtype = 'mysql'; + config.native.user = 'root'; + config.native.dbname = 'io-broker'; config.native.password = process.env.SQL_PASS || ''; await setup.setAdapterConfig(config.common, config.native); - setup.startController(true, function(id, obj) {}, function (id, state) { + setup.startController( + true, + function (id, obj) {}, + function (id, state) { if (onStateChanged) onStateChanged(id, state); }, async (_objects, _states) => { objects = _objects; - states = _states; + states = _states; await tests.preInit(objects, states, sendTo, adapterShortName); _done(); - }); + }, + ); }); }); @@ -114,34 +95,44 @@ describe(`Test ${__filename}`, function() { this.timeout(60000); checkConnectionOfAdapter(function () { now = new Date().getTime(); - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.memHeapTotal', - options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'String' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.uptime', + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.memHeapTotal', options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'Boolean' - } - }, function (result) { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'String', + }, + }, + function (result) { expect(result.error).to.be.undefined; expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 10000); - }); - }); + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.uptime', + options: { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'Boolean', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 10000); + }, + ); + }, + ); }); }); @@ -150,10 +141,10 @@ describe(`Test ${__filename}`, function() { it(`Test ${__filename}: Check Datapoint Types`, function (done) { this.timeout(5000); - sendTo('sql.0', 'query', "SELECT id, name, type FROM iobroker.datapoints", function (result) { + sendTo('sql.0', 'query', 'SELECT id, name, type FROM iobroker.datapoints', function (result) { console.log(`MySQL: ${JSON.stringify(result.result, null, 2)}`); expect(result.result.length).to.least(3); - for (var i = 0; i < result.result.length; i++) { + for (let i = 0; i < result.result.length; i++) { if (result.result[i].name === 'sql.0.testValue') { expect(result.result[i].type).to.be.equal(0); } else if (result.result[i].name === 'sql.0.testValueDebounce') { diff --git a/test/testMySQLExisting.js b/test/testMySQLExisting.js index 7790b0ef..2801240f 100644 --- a/test/testMySQLExisting.js +++ b/test/testMySQLExisting.js @@ -1,4 +1,4 @@ -/* jshint -W097 */// jshint strict:false +/* jshint -W097 */ // jshint strict:false /*jslint node: true */ /*jshint expr: true*/ const expect = require('chai').expect; @@ -6,9 +6,8 @@ const setup = require('./lib/setup'); const tests = require('./lib/testcases'); let objects = null; -let states = null; +let states = null; let onStateChanged = null; -let onObjectChanged = null; let sendToID = 1; const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1); @@ -16,42 +15,20 @@ const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf(' let now = new Date().getTime(); function checkConnectionOfAdapter(cb, counter) { - counter = counter || 0; + counter ||= 0; if (counter > 20) { - cb && cb('Cannot check connection'); + cb?.('Cannot check connection'); return; } - states.getState(`system.adapter.${adapterShortName}.0.alive`, function (err, state) { - if (err) console.error(`MySQL: ${err}`); - if (state && state.val) { - cb && cb(); - } else { - setTimeout(function () { - checkConnectionOfAdapter(cb, counter + 1); - }, 1000); + states.getState(`system.adapter.${adapterShortName}.0.alive`, (err, state) => { + if (err) { + console.error(`SQLite:${err}`); } - }); -} - -function checkValueOfState(id, value, cb, counter) { - counter = counter || 0; - if (counter > 20) { - cb && cb(`Cannot check value Of State ${id}`); - return; - } - - states.getState(id, function (err, state) { - if (err) console.error(`MySQL: ${err}`); - if (value === null && !state) { - cb && cb(); - } else - if (state && (value === undefined || state.val === value)) { - cb && cb(); + if (state?.val) { + cb?.(); } else { - setTimeout(function () { - checkValueOfState(id, value, cb, counter + 1); - }, 500); + setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); } }); } @@ -64,48 +41,52 @@ function sendTo(target, command, message, callback) { }; states.pushMessage(`system.adapter.${target}`, { - command: command, - message: message, - from: 'system.adapter.test.0', + command: command, + message: message, + from: 'system.adapter.test.0', callback: { message: message, - id: sendToID++, - ack: false, - time: (new Date()).getTime() - } + id: sendToID++, + ack: false, + time: new Date().getTime(), + }, }); } -describe(`Test ${__filename}`, function() { +describe(`Test ${__filename}`, function () { before(`Test ${__filename} Start js-controller`, function (_done) { this.timeout(600000); // because of first install from npm setup.adapterStarted = false; setup.setupController(async function () { - var config = await setup.getAdapterConfig(); + const config = await setup.getAdapterConfig(); // enable adapter - config.common.enabled = true; + config.common.enabled = true; config.common.loglevel = 'debug'; config.native.enableDebugLogs = true; config.native.host = '127.0.0.1'; - config.native.dbtype = 'mysql'; - config.native.user = 'root'; + config.native.dbtype = 'mysql'; + config.native.user = 'root'; config.native.password = process.env.SQL_PASS || ''; await setup.setAdapterConfig(config.common, config.native); - setup.startController(true, function(id, obj) {}, function (id, state) { + setup.startController( + true, + function (id, obj) {}, + function (id, state) { if (onStateChanged) onStateChanged(id, state); }, async (_objects, _states) => { objects = _objects; - states = _states; + states = _states; await tests.preInit(objects, states, sendTo, adapterShortName); _done(); - }); + }, + ); }); }); @@ -113,34 +94,44 @@ describe(`Test ${__filename}`, function() { this.timeout(60000); checkConnectionOfAdapter(function () { now = new Date().getTime(); - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.memHeapTotal', - options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'String' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.uptime', + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.memHeapTotal', options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'Boolean' - } - }, function (result) { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'String', + }, + }, + function (result) { expect(result.error).to.be.undefined; expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 10000); - }); - }); + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.uptime', + options: { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'Boolean', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 10000); + }, + ); + }, + ); }); }); @@ -149,10 +140,10 @@ describe(`Test ${__filename}`, function() { it(`Test ${__filename}: Check Datapoint Types`, function (done) { this.timeout(5000); - sendTo('sql.0', 'query', "SELECT id, name, type FROM iobroker.datapoints", function (result) { + sendTo('sql.0', 'query', 'SELECT id, name, type FROM iobroker.datapoints', function (result) { console.log(`MySQL: ${JSON.stringify(result.result, null, 2)}`); expect(result.result.length).to.least(3); - for (var i = 0; i < result.result.length; i++) { + for (let i = 0; i < result.result.length; i++) { if (result.result[i].name === 'sql.0.testValue') { expect(result.result[i].type).to.be.equal(0); } else if (result.result[i].name === 'sql.0.testValueDebounce') { diff --git a/test/testMySQLExistingNoNulls.js b/test/testMySQLExistingNoNulls.js index 614fdbf1..117fee7c 100644 --- a/test/testMySQLExistingNoNulls.js +++ b/test/testMySQLExistingNoNulls.js @@ -1,4 +1,4 @@ -/* jshint -W097 */// jshint strict:false +/* jshint -W097 */ // jshint strict:false /*jslint node: true */ /*jshint expr: true*/ const expect = require('chai').expect; @@ -6,9 +6,8 @@ const setup = require('./lib/setup'); const tests = require('./lib/testcases'); let objects = null; -let states = null; +let states = null; let onStateChanged = null; -let onObjectChanged = null; let sendToID = 1; const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1); @@ -16,42 +15,20 @@ const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf(' let now = new Date().getTime(); function checkConnectionOfAdapter(cb, counter) { - counter = counter || 0; + counter ||= 0; if (counter > 20) { - cb && cb('Cannot check connection'); + cb?.('Cannot check connection'); return; } - states.getState(`system.adapter.${adapterShortName}.0.alive`, function (err, state) { - if (err) console.error(`MySQL: ${err}`); - if (state && state.val) { - cb && cb(); - } else { - setTimeout(function () { - checkConnectionOfAdapter(cb, counter + 1); - }, 1000); + states.getState(`system.adapter.${adapterShortName}.0.alive`, (err, state) => { + if (err) { + console.error(`SQLite:${err}`); } - }); -} - -function checkValueOfState(id, value, cb, counter) { - counter = counter || 0; - if (counter > 20) { - cb && cb(`Cannot check value Of State ${id}`); - return; - } - - states.getState(id, function (err, state) { - if (err) console.error(`MySQL: ${err}`); - if (value === null && !state) { - cb && cb(); - } else - if (state && (value === undefined || state.val === value)) { - cb && cb(); + if (state?.val) { + cb?.(); } else { - setTimeout(function () { - checkValueOfState(id, value, cb, counter + 1); - }, 500); + setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); } }); } @@ -64,49 +41,53 @@ function sendTo(target, command, message, callback) { }; states.pushMessage(`system.adapter.${target}`, { - command: command, - message: message, - from: 'system.adapter.test.0', + command: command, + message: message, + from: 'system.adapter.test.0', callback: { message: message, - id: sendToID++, - ack: false, - time: (new Date()).getTime() - } + id: sendToID++, + ack: false, + time: new Date().getTime(), + }, }); } -describe(`Test ${__filename}`, function() { +describe(`Test ${__filename}`, function () { before(`Test ${__filename} Start js-controller`, function (_done) { this.timeout(600000); // because of first install from npm setup.adapterStarted = false; setup.setupController(async function () { - var config = await setup.getAdapterConfig(); + const config = await setup.getAdapterConfig(); // enable adapter - config.common.enabled = true; + config.common.enabled = true; config.common.loglevel = 'debug'; config.native.enableDebugLogs = true; config.native.writeNulls = false; config.native.host = '127.0.0.1'; - config.native.dbtype = 'mysql'; - config.native.user = 'root'; + config.native.dbtype = 'mysql'; + config.native.user = 'root'; config.native.password = process.env.SQL_PASS || ''; await setup.setAdapterConfig(config.common, config.native); - setup.startController(true, function(id, obj) {}, function (id, state) { + setup.startController( + true, + function (id, obj) {}, + function (id, state) { if (onStateChanged) onStateChanged(id, state); }, - async (_objects, _states) =>{ + async (_objects, _states) => { objects = _objects; - states = _states; + states = _states; await tests.preInit(objects, states, sendTo, adapterShortName); _done(); - }); + }, + ); }); }); @@ -114,34 +95,44 @@ describe(`Test ${__filename}`, function() { this.timeout(60000); checkConnectionOfAdapter(function () { now = new Date().getTime(); - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.memHeapTotal', - options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'String' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.uptime', + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.memHeapTotal', options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'Boolean' - } - }, function (result) { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'String', + }, + }, + function (result) { expect(result.error).to.be.undefined; expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 10000); - }); - }); + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.uptime', + options: { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'Boolean', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 10000); + }, + ); + }, + ); }); }); @@ -150,10 +141,10 @@ describe(`Test ${__filename}`, function() { it(`Test ${__filename}: Check Datapoint Types`, function (done) { this.timeout(5000); - sendTo('sql.0', 'query', "SELECT id, name, type FROM iobroker.datapoints", function (result) { + sendTo('sql.0', 'query', 'SELECT id, name, type FROM iobroker.datapoints', function (result) { console.log(`MySQL: ${JSON.stringify(result.result, null, 2)}`); expect(result.result.length).to.least(3); - for (var i = 0; i < result.result.length; i++) { + for (let i = 0; i < result.result.length; i++) { if (result.result[i].name === 'sql.0.testValue') { expect(result.result[i].type).to.be.equal(0); } else if (result.result[i].name === 'sql.0.testValueDebounce') { diff --git a/test/testPackageFiles.js b/test/testPackageFiles.js index a63e6bcf..62a4988b 100644 --- a/test/testPackageFiles.js +++ b/test/testPackageFiles.js @@ -5,7 +5,7 @@ 'use strict'; const expect = require('chai').expect; -const fs = require('fs'); +const fs = require('node:fs'); describe('Test package.json and io-package.json', () => { it('Test package files', done => { @@ -23,10 +23,15 @@ describe('Test package.json and io-package.json', () => { expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist; expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist; - expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version); + expect( + ioPackage.common.version, + 'ERROR: Version numbers in package.json and io-package.json needs to match', + ).to.be.equal(npmPackage.version); if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) { - console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!'); + console.log( + 'WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!', + ); console.log(); } @@ -37,20 +42,30 @@ describe('Test package.json and io-package.json', () => { if (ioPackage.common.name.indexOf('template') !== 0) { if (Array.isArray(ioPackage.common.authors)) { - expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0); + expect( + ioPackage.common.authors.length, + 'ERROR: Author in io-package.json needs to be set', + ).to.not.be.equal(0); if (ioPackage.common.authors.length === 1) { - expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); + expect( + ioPackage.common.authors[0], + 'ERROR: Author in io-package.json needs to be a real name', + ).to.not.be.equal('my Name '); } + } else { + expect( + ioPackage.common.authors, + 'ERROR: Author in io-package.json needs to be a real name', + ).to.not.be.equal('my Name '); } - else { - expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); - } - } - else { + } else { console.log('WARNING: Testing for set authors field in io-package skipped because template adapter'); console.log(); } - expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true; + expect( + fs.existsSync(__dirname + '/../README.md'), + 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.', + ).to.be.true; if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') { console.log('WARNING: titleLang is not existing in io-package.json. Please add'); console.log(); @@ -61,17 +76,26 @@ describe('Test package.json and io-package.json', () => { ioPackage.common.title.indexOf('adapter') !== -1 || ioPackage.common.title.indexOf('Adapter') !== -1 ) { - console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.'); + console.log( + 'WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.', + ); console.log(); } if (!ioPackage.common.controller && !ioPackage.common.onlyWWW && !ioPackage.common.noConfig) { - if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) { + if ( + !ioPackage.common.materialize || + !fs.existsSync(__dirname + '/../admin/index_m.html') || + !fs.existsSync(__dirname + '/../gulpfile.js') + ) { console.log('WARNING: Admin3 support is missing! Please add it'); console.log(); } if (ioPackage.common.materialize) { - expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true; + expect( + fs.existsSync(__dirname + '/../admin/index_m.html'), + 'Admin3 support is enabled in io-package.json, but index_m.html is missing!', + ).to.be.true; } } @@ -81,7 +105,10 @@ describe('Test package.json and io-package.json', () => { console.log('Warning: The README.md should have a section ## Changelog'); console.log(); } - expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true; + expect( + licenseFileExists || fileContentReadme.indexOf('## License') !== -1, + 'A LICENSE must exist as LICENSE file or as part of the README.md', + ).to.be.true; if (!licenseFileExists) { console.log('Warning: The License should also exist as LICENSE file'); console.log(); diff --git a/test/testPostgreSQL.js b/test/testPostgreSQL.js index 825f55b1..24b88ec0 100644 --- a/test/testPostgreSQL.js +++ b/test/testPostgreSQL.js @@ -1,4 +1,4 @@ -/* jshint -W097 */// jshint strict:false +/* jshint -W097 */ // jshint strict:false /*jslint node: true */ /*jshint expr: true*/ const expect = require('chai').expect; @@ -6,9 +6,8 @@ const setup = require('./lib/setup'); const tests = require('./lib/testcases'); let objects = null; -let states = null; +let states = null; let onStateChanged = null; -let onObjectChanged = null; let sendToID = 1; const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1); @@ -16,42 +15,20 @@ const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf(' let now = new Date().getTime(); function checkConnectionOfAdapter(cb, counter) { - counter = counter || 0; + counter ||= 0; if (counter > 20) { - cb && cb('Cannot check connection'); + cb?.('Cannot check connection'); return; } - states.getState(`system.adapter.${adapterShortName}.0.alive`, function (err, state) { - if (err) console.error(`PostgreSQL: ${err}`); - if (state && state.val) { - cb && cb(); - } else { - setTimeout(function () { - checkConnectionOfAdapter(cb, counter + 1); - }, 1000); + states.getState(`system.adapter.${adapterShortName}.0.alive`, (err, state) => { + if (err) { + console.error(`SQLite:${err}`); } - }); -} - -function checkValueOfState(id, value, cb, counter) { - counter = counter || 0; - if (counter > 20) { - cb && cb(`Cannot check value Of State ${id}`); - return; - } - - states.getState(id, function (err, state) { - if (err) console.error(`PostgreSQL: ${err}`); - if (value === null && !state) { - cb && cb(); - } else - if (state && (value === undefined || state.val === value)) { - cb && cb(); + if (state?.val) { + cb?.(); } else { - setTimeout(function () { - checkValueOfState(id, value, cb, counter + 1); - }, 500); + setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); } }); } @@ -64,48 +41,52 @@ function sendTo(target, command, message, callback) { }; states.pushMessage(`system.adapter.${target}`, { - command: command, - message: message, - from: 'system.adapter.test.0', + command: command, + message: message, + from: 'system.adapter.test.0', callback: { message: message, - id: sendToID++, - ack: false, - time: (new Date()).getTime() - } + id: sendToID++, + ack: false, + time: new Date().getTime(), + }, }); } -describe(`Test ${__filename}`, function() { +describe(`Test ${__filename}`, function () { before(`Test ${__filename} Start js-controller`, function (_done) { this.timeout(600000); // because of first install from npm setup.adapterStarted = false; setup.setupController(async function () { - var config = await setup.getAdapterConfig(); + const config = await setup.getAdapterConfig(); // enable adapter - config.common.enabled = true; + config.common.enabled = true; config.common.loglevel = 'debug'; config.native.enableDebugLogs = true; config.native.host = '127.0.0.1'; - config.native.dbtype = 'postgresql'; - config.native.user = 'postgres'; + config.native.dbtype = 'postgresql'; + config.native.user = 'postgres'; config.native.password = process.env.SQL_PASS || ''; await setup.setAdapterConfig(config.common, config.native); - setup.startController(true, function(id, obj) {}, function (id, state) { + setup.startController( + true, + function (id, obj) {}, + function (id, state) { if (onStateChanged) onStateChanged(id, state); }, async (_objects, _states) => { objects = _objects; - states = _states; + states = _states; await tests.preInit(objects, states, sendTo, adapterShortName); _done(); - }); + }, + ); }); }); @@ -113,34 +94,44 @@ describe(`Test ${__filename}`, function() { this.timeout(60000); checkConnectionOfAdapter(function () { now = new Date().getTime(); - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.memHeapTotal', - options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'String' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.uptime', + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.memHeapTotal', options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'Boolean' - } - }, function (result) { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'String', + }, + }, + function (result) { expect(result.error).to.be.undefined; expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 10000); - }); - }); + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.uptime', + options: { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'Boolean', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 10000); + }, + ); + }, + ); }); }); @@ -149,10 +140,10 @@ describe(`Test ${__filename}`, function() { it(`Test ${__filename}: Check Datapoint Types`, function (done) { this.timeout(5000); - sendTo('sql.0', 'query', "SELECT name, type FROM datapoints", function (result) { + sendTo('sql.0', 'query', 'SELECT name, type FROM datapoints', function (result) { console.log(`PostgreSQL: ${JSON.stringify(result.result, null, 2)}`); expect(result.result.length).to.least(3); - for (var i = 0; i < result.result.length; i++) { + for (let i = 0; i < result.result.length; i++) { if (result.result[i].name === 'sql.0.testValue') { expect(result.result[i].type).to.be.equal(0); } else if (result.result[i].name === 'sql.0.testValueDebounce') { diff --git a/test/testSQLite.js b/test/testSQLite.js index 3cb25d5e..90a50e72 100644 --- a/test/testSQLite.js +++ b/test/testSQLite.js @@ -1,14 +1,13 @@ -/* jshint -W097 */// jshint strict:false +/* jshint -W097 */ // jshint strict:false /*jslint node: true */ /*jshint expr: true*/ const expect = require('chai').expect; -var setup = require('./lib/setup'); +const setup = require('./lib/setup'); const tests = require('./lib/testcases'); let objects = null; -let states = null; +let states = null; let onStateChanged = null; -let onObjectChanged = null; let sendToID = 1; const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1); @@ -16,42 +15,20 @@ const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf(' let now = new Date().getTime(); function checkConnectionOfAdapter(cb, counter) { - counter = counter || 0; + counter ||= 0; if (counter > 20) { - cb && cb('Cannot check connection'); + cb?.('Cannot check connection'); return; } - states.getState(`system.adapter.${adapterShortName}.0.alive`, function (err, state) { - if (err) console.error(`SQLite:${err}`); - if (state && state.val) { - cb && cb(); - } else { - setTimeout(function () { - checkConnectionOfAdapter(cb, counter + 1); - }, 1000); + states.getState(`system.adapter.${adapterShortName}.0.alive`, (err, state) => { + if (err) { + console.error(`SQLite:${err}`); } - }); -} - -function checkValueOfState(id, value, cb, counter) { - counter = counter || 0; - if (counter > 20) { - cb && cb(`Cannot check value Of State ${id}`); - return; - } - - states.getState(id, function (err, state) { - if (err) console.error(`SQLite:${err}`); - if (value === null && !state) { - cb && cb(); - } else - if (state && (value === undefined || state.val === value)) { - cb && cb(); + if (state?.val) { + cb?.(); } else { - setTimeout(function () { - checkValueOfState(id, value, cb, counter + 1); - }, 500); + setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); } }); } @@ -64,45 +41,49 @@ function sendTo(target, command, message, callback) { }; states.pushMessage(`system.adapter.${target}`, { - command: command, - message: message, - from: 'system.adapter.test.0', + command: command, + message: message, + from: 'system.adapter.test.0', callback: { message: message, - id: sendToID++, - ack: false, - time: (new Date()).getTime() - } + id: sendToID++, + ack: false, + time: new Date().getTime(), + }, }); } -describe(`Test ${__filename}`, function() { +describe(`Test ${__filename}`, function () { before(`Test ${__filename} Start js-controller`, function (_done) { this.timeout(600000); // because of first install from npm setup.adapterStarted = false; setup.setupController(async function () { - var config = await setup.getAdapterConfig(); + const config = await setup.getAdapterConfig(); // enable adapter - config.common.enabled = true; + config.common.enabled = true; config.common.loglevel = 'debug'; config.native.enableDebugLogs = true; - config.native.dbtype = 'sqlite'; + config.native.dbtype = 'sqlite'; await setup.setAdapterConfig(config.common, config.native); - setup.startController(true, function(id, obj) {}, function (id, state) { + setup.startController( + true, + function (id, obj) {}, + function (id, state) { if (state) onStateChanged(id, state); }, async (_objects, _states) => { objects = _objects; - states = _states; + states = _states; await tests.preInit(objects, states, sendTo, adapterShortName); _done(); - }); + }, + ); }); }); @@ -110,34 +91,44 @@ describe(`Test ${__filename}`, function() { this.timeout(60000); checkConnectionOfAdapter(function () { now = new Date().getTime(); - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.memHeapTotal', - options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'String' - } - }, function (result) { - expect(result.error).to.be.undefined; - expect(result.success).to.be.true; - sendTo('sql.0', 'enableHistory', { - id: 'system.adapter.sql.0.uptime', + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.memHeapTotal', options: { - changesOnly: false, - debounce: 0, - retention: 31536000, - storageType: 'Boolean' - } - }, function (result) { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'String', + }, + }, + function (result) { expect(result.error).to.be.undefined; expect(result.success).to.be.true; - // wait till adapter receives the new settings - setTimeout(function () { - done(); - }, 10000); - }); - }); + sendTo( + 'sql.0', + 'enableHistory', + { + id: 'system.adapter.sql.0.uptime', + options: { + changesOnly: false, + debounce: 0, + retention: 31536000, + storageType: 'Boolean', + }, + }, + function (result) { + expect(result.error).to.be.undefined; + expect(result.success).to.be.true; + // wait till adapter receives the new settings + setTimeout(function () { + done(); + }, 10000); + }, + ); + }, + ); }); }); @@ -146,10 +137,10 @@ describe(`Test ${__filename}`, function() { it(`Test ${__filename}: Check Datapoint Types`, function (done) { this.timeout(10000); - sendTo('sql.0', 'query', "SELECT name, type FROM datapoints", function (result) { + sendTo('sql.0', 'query', 'SELECT name, type FROM datapoints', function (result) { console.log(`SQLite: ${JSON.stringify(result.result, null, 2)}`); expect(result.result.length).to.least(3); - for (var i = 0; i < result.result.length; i++) { + for (let i = 0; i < result.result.length; i++) { if (result.result[i].name === 'sql.0.testValue') { expect(result.result[i].type).to.be.equal(0); } else if (result.result[i].name === 'sql.0.testValueDebounce') { @@ -161,9 +152,7 @@ describe(`Test ${__filename}`, function() { } } - setTimeout(function () { - done(); - }, 5000); + setTimeout(() => done(), 5000); }); }); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..d5fa0c65 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowJs": false, + "checkJs": false, + "noEmit": false, + "declaration": false, + "types": ["@iobroker/types"] + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"], +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..ca1798d5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "noEmit": true, + "allowJs": true, + "checkJs": true, + "skipLibCheck": true, // Don't report errors in 3rd party definitions + "noEmitOnError": true, + "outDir": "./build", + "removeComments": false, + "module": "Node16", + "moduleResolution": "node16", + "esModuleInterop": true, + "resolveJsonModule": true, + "strict": true, + "target": "es2022", + "sourceMap": true, + "inlineSourceMap": false, + "useUnknownInCatchVariables": false, + "types": ["@iobroker/types"] + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +}