lamed_table
Version:
Light table implementation
936 lines (860 loc) • 34.1 kB
JavaScript
// Comment out in test functions
console.log(`Starting ${__filename}...`) // comment line to remove simple logging
/* ------------------------------------------------------
* table.js
* Purpose: The purpose of this module is to manage arrays as a table (cols, rows)
* Date Created: 2019/12/12
* Created by : Perez Lamed van Niekerk
--------------------------------------------------------- */
/* jshint esversion: 6 */
const _test = require('lamed_test')
const { Ok, notOk, notOk_Then, con, unZip } = _test // eslint-disable-line
con.useChalk(require('chalk'))
// con.traceSet(0)
con.trace('Starting table.js...')
require('ltype_string')
const _array = require('ltype_array')
const _no = require('ltype_number')
// const _convert = require('ltype_convert')
const _name = require('lamed_name')
const _table = require('./tableFunctions')
const _cols = require('./tableCols')
const _rows = require('./tableRows')
const _key = require('lamed_keyvalue')
/**
* Generate the code require to populate a table with values
* @param {object} table The table object
* @param {bool} showResult - if true then show the result
*/
function GenerateRowSet (table, showResult = true) {
const name = table.name
let result = `// Table [${name}] -> ['${table.cols.join("',")}']\n`
result += 'let row = []\n'
for (let ii = 0; ii < table.cols.length; ii++) {
const col = table.cols[ii]
result += `${name}.Rows.Set(row, '${col}', '???')\n`
}
result += `${name}.Rows.Add(row)\n`
if (showResult) { con.logLine(); con.logRed(result); con.logLine() }
return result
}
/**
* Show a generated template of code to loop through the table values
* @param {object} table - The table object
* @param {string} varName - The table variable name
*/
function GenerateTableLoop (table, showResult = true, varName = '') {
let name = varName
if (name === '') name = _name.nameSafe(table.name)
let result = `// ${name} -> [${table.cols.join(', ')}]\n`
result += ` for (let ii = 0; ii < ${name}.DATA.rows.length; ii++) {\n`
result += ` let row = ${name}.DATA.rows[ii]\n`
result += GenerateRow(table, false)
result += '}'
if (showResult) { con.logLine(); con.logRed(result); con.logLine() }
return result
}
/**
* For a given table, generate the code to get all the values of a row into variables
* @param {object} table - The table object
* @param {bool} showResult - If true then display the results
*/
function GenerateRow (table, showResult = true) {
let result = ' let [ ' + table.cols.join(', ') + ' ] = row\n'
result += ' con.log({ ' + table.cols.join(', ') + ' })\n'
if (showResult) { con.logLine(); con.logRed(result); con.logLine() }
return result
}
/**
* Generate a sample row add code
* @param {table} table - The table
* @param {bool} showResult - If true, show the result
* @returns {string} - The generated code
*/
function GenerateAdd (table, showResult = true) {
const result = `${table.name}.Rows.Add([${table.cols.join(', ')}])`
if (showResult) { con.logLine(); con.logRed(result); con.logLine() }
return result
}
/**
* Code generation functions
*/
function CodeGenerate (table) {
this.DATA = table
}
CodeGenerate.prototype.RowGet = function (showResult = true) {
return GenerateRow(this.DATA, showResult)
}
CodeGenerate.prototype.TableLoop = function (showResult = true, varName = '') {
return GenerateTableLoop(this.DATA, showResult, varName)
}
CodeGenerate.prototype.RowSet = function (showResult = true) {
return GenerateRowSet(this.DATA, showResult)
}
/* GenerateAdd */
CodeGenerate.prototype.RowAdd = function (showResult = true) {
return GenerateAdd(this.DATA, showResult)
}
/**
* Clone an existing table using the specified columns
* @param {object} table - The table to clone from
* @param {array} columns - The column names to clone
* @param {*} tableName - The name of the new table
*/
function cloneTable (table, columns, tableName) {
if (notOk(tableName)) {
if (Ok(table.name)) tableName = table.name + 'Clone' // Table has no name
else tableName = 'Clone'
} else {
if (tableName.includes('-->')) tableName = table.name + tableName // This shows history of clones
}
if (notOk(columns) || columns.length === 0) columns = table.cols
const result = new TableDef(columns, tableName)
const rows = table.rows
for (let ii = 0; ii < rows.length; ii++) {
// Rows
const row = rows[ii]
const row1 = []
let value = -1
for (let jj = 0; jj < columns.length; jj++) {
// Columns
const colName = columns[jj]
const colNo = _cols.colsNumber(table.cols, colName, false)
if (colNo === -1) value = '' // This is a new column
else value = row[colNo]
result.Rows.Set(row1, colName, value)
}
result.Rows.Add(row1)
}
// result.check(true)
return result
}
/**
* Clone table and Drop the specified columns
* @param {table} table - Table object
* @param {number / string / array} colNameOrNumber - The table number or name
* @returns {table} - The cloned table
*/
function cloneTableDropColumn (table, colNameOrNumbers) {
const colsNew = table.cols.slice(0) // clone cols array
let items = [colNameOrNumbers]
if (Array.isArray(colNameOrNumbers)) items = colNameOrNumbers
for (let ii = 0; ii < items.length; ii++) {
const item = items[ii]
const index = _cols.colsNumber(colsNew, item)
colsNew.splice(index, 1)
}
return cloneTable(table, colsNew, table.name)
}
/**
* Compare two values
* @param {any} item - Item to compare
* @param {any} search - The search value
* @param {string} operator - One of '='; '===' ; '>'; '>='; '<'; '<='; '!='; '!=='; 'like'
*/
function compareValue (item, search, operator = '=') {
if (operator === '=' || operator === '===') return (item === search)
else if (operator === '>') return (item > search)
else if (operator === '>=') return (item >= search)
else if (operator === '<') return (item < search)
else if (operator === '<=') return (item <= search)
else if (operator === '!=' || operator === '!==') return (item !== search)
else if (operator === 'like') return item.includes(search)
else throw new Error(`Operator '${operator}' not supported!\n`)
}
unZip(() => compareValue('val1', 'val2'), false)
unZip(() => compareValue('val1', 'val1'), true)
unZip(() => compareValue(10, 10), true)
unZip(() => compareValue(10, 5, '>'), true)
unZip(() => compareValue(10, 5, '>='), true)
unZip(() => compareValue(10, 7, '<'), false)
unZip(() => compareValue(10, 7, '<='), false)
unZip(() => compareValue(7, 8, '!='), true)
unZip(() => compareValue('this is the row', 'row'), false)
unZip(() => compareValue('this is the row', 'row', 'like'), true)
/**
* Find a string in a specific column
* @param {object} table - Table object
* @param {string} colName - Column name
* @param {string} value - value to search for
* @param {bool} findFirst - if true, return the first positive
*/
function tableFind (table, colName, value, findFirst = false, operator = '=') {
operator = operator.toLowerCase() // ensure 'like'
const rows = []
const colNo = _cols.colsNumber(table.cols, colName)
const isStr = (typeof value === 'string')
if (isStr) value = value.toLowerCase()
for (let ii = 0; ii < table.rows.length; ii++) {
const row = table.rows[ii]
let item = row[colNo]
if (isStr) item = item.toLowerCase()
if (compareValue(item, value, operator)) {
if (findFirst) return row
else rows.push(row)
}
}
if (rows.length !== 0) {
// Lets return a table
const result = new TableDef(table.cols, 'rowFind')
result.DATA.rows = rows
return result
}
}
/**
* Return unique cloned table
* @param {table} table - The table to work on
* @returns {table} - Unique cloned table
*/
function tableUnique (table) {
const cols = table.cols
const result = new TableDef(cols, 'unique')
const rows = table.rows
const keys = []
// Let as loop the input and build the output
for (let ii = 0; ii < rows.length; ii++) {
// Read Rows
const row = rows[ii]
let key = ''
for (let jj = 0; jj < cols.length; jj++) {
// Read Columns
const value = row[jj]
// Create key based on all values in the row
key += value + '_' // Add char to ensure key is unique
}
if (keys.indexOf(key) === -1) {
// This is a new key --> add the data
keys.push(key)
result.Rows.Add(row)
}
}
return result
}
/**
* Merge table2 with first table.
* @param {table} tableData - The table to merge to.
* @param {table} table2 - If the value is not in the input table, then add it
* @param {boolean} removeDuplicates - If true then duplicates are removed
* @returns {table} - Returns cloned merged table
*/
function tableMerge (tableData, table2, removeDuplicates = false) {
let result = cloneTable(tableData)
const rows = table2.DATA.rows
for (let ii = 0; ii < rows.length; ii++) {
const row = rows[ii] // Read Rows
result.Rows.Add(row)
}
if (removeDuplicates) result = tableUnique(result.DATA)
return result
}
/**
* Show statistics about a table
* @param {table} table - The table object
* @param {bool} show - If true then display the table statistics
* @returns {object}
*/
function tableStats (table, show = true, details = false) {
const rows = table.DATA.rows.length
const cols = table.DATA.cols
const fieldNames = []
const result = { name: table.DATA.name, columns: cols.length, rows, fields: [] }
// field, unique, uniquePart, uniqueStr, multiple
const dtField = new TableDef(['field', 'unique', 'uniquePart', 'uniqueStr', 'multiple', 'samples'], 'FieldStats')
const dtStats = new TableDef(['column', 'value', 'count'], 'dtStats')
dtStats.Rows.Add([result.name, 'columns', result.columns])
dtStats.Rows.Add([result.name, 'rows', result.rows])
if (show) {
con.logLine()
con.logGreen(`Table: '${result.name}'`)
con.log(`Columns: ${result.columns}`)
con.log(`Rows: ${result.rows}`)
con.log('----[Column descriptions]----')
}
// let tables = []
for (let ii = 0; ii < cols.length; ii++) {
const field = cols[ii]
const group = table.groupBy(field)
// group.show(true, 10)
const unique = group.DATA.rows.length
const uniquePart = unique * 100 / rows
const uniqueStr = _no.toFixedStr(uniquePart, 2) + '%'
// Doubles ---------------------------------
const filter = group.filterNew()
filter.add('count', 1, '>')
const dblTable = group.filter(filter, [], false, false)
// con.log({ dblTable })
let multiple = 0
if (Ok(dblTable)) {
multiple = dblTable.totalRows()
dblTable.sort('count', false)
dblTable.DATA.name = field
if (unique < 26) {
// Copy results to dtStats
// con.log({ field })
fieldNames.push(field)
for (let ii = 0; ii < dblTable.DATA.rows.length; ii++) {
const row = dblTable.DATA.rows[ii]
const [fieldValue, countValue] = row
// con.log({ field, fieldValue, countValue })
dtStats.Rows.Add([field, fieldValue, countValue])
}
}
}
if (field === 'duration') {
// Lets provide more info for durations
const dtDuration = group.groupBy('', false, 'duration')
dtStats.Rows.Add(['duration', 'avg', dtDuration.DATA.avg])
dtStats.Rows.Add(['duration', 'min', dtDuration.DATA.min])
dtStats.Rows.Add(['duration', 'max', dtDuration.DATA.max])
dtStats.Rows.Add(['duration', 'sum', dtDuration.DATA.sum])
}
let samples = table.Cols.toArray(field).join(', ')
samples = samples.substr(0, 45)
if (show) {
// Calculate space
let len = 20 - field.length
if (len < 0) len = 0
if (ii > 8) len--
const space = ' '.repeat(len)
// Show fields
const output = `${ii + 1}. ${field}${space} --> ${unique} unique and ${multiple} multiples; (${uniqueStr}); [${samples}...]`
if (uniquePart > 70) con.logBold(output)
else con.log(output)
}
dtField.Rows.Add([field, unique, uniquePart, uniqueStr, multiple, samples])
}
if (details) {
// Show the counts
con.logLine()
dtStats.show()
con.logLine()
}
result.fields = dtField
return { fieldNames, tableStats: result, dataStats: dtStats }
}
/**
* Group a table by its columns. Also calculate totals
* @param {array} groupCols - The columns array
* @param {bool} show - If true, show the result
* @param {string} sumColumn - The column to calculate total sum for. It shall be a column that contains numbers
*/
function tableGroupBy (table, groupCols, show = false, sumColumn = '') {
// Check the input and try to correct wrong values
if (Array.isArray(sumColumn)) sumColumn = sumColumn[0] // sumColumn can not be an array
if (Array.isArray(groupCols) === false) {
// groupCols must be array
if (typeof groupCols === 'boolean') {
show = groupCols // No groupCols & bool value --> use this as the show setting
groupCols = []
} else if (notOk(groupCols)) groupCols = []
else groupCols = [groupCols]
}
// Check if groupCols is in table
groupCols = _cols.colsNames(table.cols, groupCols)
con.log({ groupCols })
// for (let ii = 0; ii < groupCols.length; ii++) {
// const item = groupCols[ii]
// _cols.colsNumber(table.cols, item, true) // Throws error if not found
// }
// -------------------------------------------------------------
// Get clone of the table of only the fields we are interested in
groupCols.push('key')
groupCols.push('count')
if (sumColumn !== '') groupCols.push(`${sumColumn}_sum`)
// Define output table
let result = new TableDef(groupCols, table.name)
// con.log({ groupCols })
if (sumColumn !== '' && groupCols.includes(sumColumn) === false) groupCols.push(sumColumn) // If sum- column is not there, add it
// con.log({ groupCols })
const clone = cloneTable(table, groupCols)
const rows = clone.DATA.rows
// let rows = table.rows // this does not work
const index = result.Cols.Number(sumColumn, false) // The group totals col
const indexCount = result.Cols.Number('count')
const indexSum = result.Cols.Number(`${sumColumn}_sum`, false)
// con.log({ sumColumn, index, indexCount, indexSum })
const keys = []; const values = []
// Let as loop the input and build the output
for (let ii = 0; ii < rows.length; ii++) {
// Read Rows
const row = rows[ii]
let key = ''; let sum = 0
for (let jj = 0; jj < groupCols.length; jj++) {
// Read Columns
const value = row[jj]
if (jj === index) { // This is the total column
sum = value // sum has bigger scope than value
values.push(value) // we will use this to calculate totals
}
if (index === -1 && jj === groupCols.length - 1) {
// Special case
sum = value // sum has bigger scope than value
values.push(value) // we will use this to calculate totals
break // Break the loop and do not update the key
}
// Create key based on values found
key += value + '_' // Add char to ensure key is unique
}
// Calculate output
if (keys.indexOf(key) === -1) {
// This is a new key
// con.log({ key })
keys.push(key)
result.Rows.Set(row, 'key', key)
result.Rows.Set(row, indexCount, 1) // count
if (indexSum !== -1) result.Rows.Set(row, `${sumColumn}_sum`, sum) // set field_sum
result.Rows.Add(row)
} else {
// The key exist, find it and add count, field_sum
const row2 = result.find('key', key, true)
row2[indexCount]++ // Count
if (indexSum !== -1) {
// calculate sum
sum = sum + row2[indexSum]
result.Rows.Set(row2, `${sumColumn}_sum`, sum)
}
}
}
result = result.cloneDropCol('key') // we no longer need the key
// Calculate Totals -----------------------
result.DATA.count = result.DATA.rows.length
if (sumColumn !== '') {
const totals = _array.totals(values)
result.DATA.totalCol = sumColumn
result.DATA.min = totals.min
result.DATA.max = totals.max
result.DATA.sum = totals.sum
result.DATA.avg = totals.avg
}
// Prepare results --------------------
if (result.DATA.cols.length === 1) {
// Only count as result - let us ignore the table and return the count
const count = result.DATA.rows[0][0]
if (show) { con.logBold('GroupBy:'); con.log({ count }) }
return count
}
// Show result and return it
result.show(show)
return result
}
/**
* calculate statistics using two fields ito duration
* @param {table} table - The Table to use as input
* @param {table} twoFieldArray - The table with field1, field2 values. let twoFields = new _table.TableDef(['field1', 'field2'], 'twoFields')
*/
function durationStats_From2Fields (table, twoFieldArray, show = true) { // eslint-disable-line
// Sample
// table.statsDuration2Fields([
// 'Assignee-->month',
// 'Assignee-->week',
// 'Assignee-->summary'
// ], show)
const dtDuration2 = new TableDef(['Field1', 'value1', 'Field2', 'value2', 'count', 'duration'], 'dtDuration2')
// twoFieldArray.CodeGenerate.TableLoop()
// twoFieldArray -> ['key-->value', 'key-->value']
const keyValues = _key.keyValueFromTagArray(twoFieldArray, _key.enumError.Error)
for (let ii = 0; ii < keyValues.length; ii++) {
const keyValue = keyValues[ii]
const { key, value } = keyValue
durationStatsCalculate2(table, key, value, dtDuration2)
}
dtDuration2.show(show)
return dtDuration2
}
function durationStatsCalculate2 (result, field, field2, dtDuration) {
const grpAssignee = tableGroupBy(result, [field, field2], false, 'duration')
for (let ii = 0; ii < grpAssignee.DATA.rows.length; ii++) {
const row = grpAssignee.DATA.rows[ii]
const [fieldValue, fieldValue2, count, duration] = row
// const avg = _convert.Convert.duration_MSec2Str((duration / count), false, false)
dtDuration.Rows.Add([field, fieldValue, field2, fieldValue2, count, duration])
}
}
/**
* Calculate duration statistics per field
* @param {table} table - The input table
* @param {array} fields - The fields
*/
function durationStats (table, fields = [], show = true) {
if (Array.isArray(fields) === false) fields = [fields]
const dtDuration = new TableDef(['Field', 'value', 'count', 'duration'], 'dtDuration')
const durationGrp = tableGroupBy(table, '', false, 'duration')
const total = durationGrp.DATA.sum
const count = durationGrp.DATA.count
dtDuration.Rows.Add(['Avg', 'duration', count, total])
for (let ii = 0; ii < fields.length; ii++) {
const field = fields[ii]
durationStatsCalculate(table, field, dtDuration)
}
dtDuration.show(show)
return dtDuration
}
function durationStatsCalculate (result, field, dtDuration) {
const grpAssignee = tableGroupBy(result, [field], false, 'duration')
for (let ii = 0; ii < grpAssignee.DATA.rows.length; ii++) {
const row = grpAssignee.DATA.rows[ii]
const [fieldValue, count, duration] = row
// const avg = _convert.Convert.duration_MSec2Str((duration / count), false, false)
dtDuration.Rows.Add([field, fieldValue, count, duration])
}
}
/**
* Calculate series statistics per month
* @param {table} table - The input table
* @param {array} fields - The fields
*/
function seriesMonth (table, fields = [], show = true) {
if (Array.isArray(fields) === false) fields = [fields]
if (fields.length === 0) fields = table.cols
if (table.cols.includesAny('month') === false) throw new Error(`No month column in ${table.cols}\n`)
const dtSeries = new TableDef(['field', 'fieldValue', 'month', 'total'], 'dtSeries')
// const dtMonth = tableGroupBy(table, 'month', false)
for (let ii = 0; ii < fields.length; ii++) {
const field = fields[ii]
if (field.includesAny('month', 'week', 'duration')) continue // skip these
seriesMonthCalculate(table, field, dtSeries)
}
dtSeries.show(show)
return dtSeries
}
function seriesMonthCalculate (result, field, dtSeries) {
const grpBy = tableGroupBy(result, [field, 'month'])
// grpBy.show()
for (let ii = 0; ii < grpBy.DATA.rows.length; ii++) {
const row = grpBy.DATA.rows[ii]
const [fieldValue, month, total] = row
dtSeries.Rows.Add([field, fieldValue, month, total])
}
}
/**
* Calculate series statistics per week
* @param {table} table - The input table
* @param {array} fields - The fields
*/
function seriesWeek (table, fields = [], show = true) {
if (Array.isArray(fields) === false) fields = [fields]
if (fields.length === 0) fields = table.cols
if (table.cols.includesAny('week') === false) throw new Error(`No week column in ${table.cols}\n`)
const dtSeries = new TableDef(['field', 'fieldValue', 'week', 'total'], 'dtSeries')
for (let ii = 0; ii < fields.length; ii++) {
const field = fields[ii]
if (field.includesAny('month', 'week', 'duration')) continue // skip these
seriesWeekCalculate(table, field, dtSeries)
}
dtSeries.show(show)
return dtSeries
}
function seriesWeekCalculate (result, field, dtSeries) {
const grpBy = tableGroupBy(result, [field, 'week'])
// grpBy.show()
for (let ii = 0; ii < grpBy.DATA.rows.length; ii++) {
const row = grpBy.DATA.rows[ii]
const [fieldValue, week, total] = row
dtSeries.Rows.Add([field, fieldValue, week, total])
}
}
/**
* Joint child table with its parent table and return new table.
* @param {table} tableMany - The many table
* @param {string} idMany - The id of the many table
* @param {table} tableParent - The parent table
* @param {string} idParent - The id of the parent table
* @param {bool} throwError - If true then throw error if no link was found else only show message
* @returns - The joined table
*/
function TableJoin (tableMany, idMany, tableParent, idParent, columns = [], throwError = true) {
// Id's
idMany = idMany.toLowerCase()
idParent = idParent.toLowerCase()
const idHeadNo = _cols.colsNumber(tableMany.cols, idMany)
// let idFilesNo = tableParent.Cols.Number(idParent)
// Cols
const colsHead = tableMany.cols
const colsFiles = tableParent.DATA.cols
let joinCols = colsHead.concat(colsFiles)
joinCols = _array.uniqueArray(joinCols)
// con.log({joinCols})
let dtJoin = cloneTable(tableMany, [], tableMany.name) // We will add the rest of the fields after join
// dtJoin -> [id, heading, hash, files, levels, level1, level2, level3]
for (let ii = 0; ii < dtJoin.DATA.rows.length; ii++) {
// Do the join
const row = dtJoin.DATA.rows[ii]
const id = row[idHeadNo]
const rowLink = tableParent.find(idParent, id, true)
if (notOk(rowLink)) {
const msg = `Join row ${ii + 1} not found ${idParent} = ${id}`
if (throwError) throw new Error(msg + '\n')
else con.logRed(msg)
dtJoin.DATA.rows[ii] = row
continue
}
// let idLink = rowLink[idFilesNo]
// if (id !== idLink) throw new Error(`id(${id}) and idLink(${idLink}) does not match\n`)
// Join the data
// let rowData = row.slice(0,colsHead.length) // Remove the joined spaces
const joinLink = ((idMany === idParent) ? rowLink.slice(1) : rowLink.slice(0)) // If link id is the same remove one
const joinRow = row.concat(joinLink)
// con.log({ row, rowLink, joinLink, joinRow })
// return
dtJoin.DATA.rows[ii] = joinRow
}
// con.logTable(dtJoin.DATA)
dtJoin.DATA.cols = joinCols
// con.log(dtJoin.DATA.rows[0])
// con.log({joinCols, columns})
if (columns.length > 0) dtJoin = dtJoin.clone(columns) // Let us only return what was asked for
// dtJoin.show()
return dtJoin // Return new table
}
// ------------------------------------------------
function Filter (table) {
this.DATA = table
this.WHERE = []
}
/* filterAdd */
Filter.prototype.add = function (column, value, operator = '=', andFilter = true) {
filterAdd(this.DATA, this.WHERE, column, value, operator, andFilter)
}
Filter.prototype.reset = function () {
this.WHERE = []
}
Filter.prototype.show = function (show = true) { filterShow(this.WHERE, show) }
/**
*
* @param {table} table - Table object
* @param {object} where - The current WHERE condition
* @param {string/number} column - The column name or number
* @param {object} value - The value to test
* @param {string} operator - One of '='; '===' ; '>'; '>='; '<'; '<='; '!='; '!=='; 'like'
* @param {boolean} andFilter - If true then filter again on previous result else filter on origin and add results.
* @returns {void} - Nothing
*/
function filterAdd (table, where, column, value, operator = '=', andFilter = true) {
// con.log({ where, column, value, operator })
const index = _cols.colsNumber(table.cols, column, true) // Verify the column
column = table.cols[index]
operator = operator.toLowerCase()
compareValue(0, 0, operator) // test operator
where.push({ column, value, operator, andFilter })
}
/**
* Show the filter
* @param {array} where - The WHERE array
* @param {bool} show - Show indicator
*/
function filterShow (where, show = true) {
if (show) {
con.logBold('Table Filter:')
con.log({ where })
if (where.length === 0) con.logRed('No filter defined!')
else con.logTable(where)
}
}
/**
*
* @param {*} table
* @param {*} filter - See [>filterAdd()<]
* @param {*} columns
* @param {*} show
* @param {*} show0Results
* @param {*} removeDuplicates - Remove duplicates from the result
*/
function filterData (table, filter, columns = [], show = false, removeDuplicates = false) {
const where = filter.WHERE
if (columns.length === 0) columns = table.cols
let resultTable = {}
resultTable.DATA = table
if (where.length === 0) resultTable = cloneTable(table)
// let resultTable = cloneTable(table)
const start = resultTable.DATA.rows.length
for (let ii = 0; ii < where.length; ii++) {
const item = where[ii]
let { column, value, operator, andFilter } = item // eslint-disable-line
// con.log({ ii })
if (andFilter || ii === 0) {
// 'and' filter; First filter can only be 'and' filter
if (Ok(resultTable)) resultTable = tableFind(resultTable.DATA, column, value, false, operator)
} else {
// 'or' filter; Do the filter on the original data
let orTable = cloneTable(table)
orTable = tableFind(orTable.DATA, column, value, false, operator)
if (Ok(orTable)) {
if (Ok(resultTable)) resultTable = tableMerge(resultTable.DATA, orTable, true)
else resultTable = orTable // There is no results to merge with
}
}
// con.log({ resultTable })
}
// Show results
if (notOk(resultTable)) {
// con.logRed(`No results found for filter!`)
return new TableDef(columns) // <------------------------------------
}
const end = resultTable.DATA.rows.length
const result = `-->Returned '${end}' of '${start}' records`
filterShow(where, show)
if (show) {
if (start === end) con.logRed(result)
else con.logGreen('\n' + result)
}
// resultTable.show(show)
resultTable = cloneTable(resultTable.DATA, columns) // Remove the extra columns
if (removeDuplicates) resultTable = tableUnique(resultTable.DATA) // Remove duplicates
resultTable.show(show)
return resultTable
}
/**
* Return a value from a table
* @param {table} table - The table to return value from
* @param {string/number} columnNameOrNo - The column name or number
* @param {number} rowNo - The row number to return value from. Default is -1 (and mean there should only be one row and data is returned from row 1)
* @returns {any} - Return the value from the specified row
*/
function getValue (table, columnNameOrNo, rowNo = -1, throwError = true) {
const rows = table.rows
if (rowNo < 0) {
if (rows.length !== 1) {
if (throwError) throw new Error(`If no rowNo specified, then there must be only one row in the table. Table has ${rows.length} rows!\n`)
else return undefined
}
rowNo = 0
}
if (rowNo > rows.length - 1) throw new Error(`Row ${rowNo} does not exist.\n`)
const colNo = _cols.colsNumber(table.cols, columnNameOrNo)
const row = rows[rowNo]
return row[colNo]
}
/**
* Return the duplicate values for a column
* @param {table} table - The table to return value from
* @param {array} columns - The columns array of string / number
* @param {bool} show - If true then show the result
*/
function qaDuplicates (table, columns, show = false) {
const dubs = tableGroupBy(table, columns)
const filter = dubs.filterNew()
filter.add(1, 1, '>')
const dubs2 = dubs.filter(filter)
dubs2.show(show)
return dubs2
}
// ----------------------------------------------------------------------
/**
* Create a new table def object
* @param {*} columns
* @param {*} tableName
*/
function TableDef (columns = [], tableName = 'tableName') {
if (_test.isObject(columns)) {
if (_table.TableCheck(columns, false, true)) {
this.DATA = columns.DATA
} else throw new Error('Cannot create table')
} else this.DATA = _table.create(columns, tableName)
this.Cols = new _cols.Cols(this.DATA)
this.CodeGenerate = new CodeGenerate(this.DATA)
this.Rows = new _rows.Rows(this.DATA)
}
// TableDef.prototype.CodeGenerate = new CodeGenerate(this.DATA)
// function () {
// return new CodeGenerate(this.DATA)
// }
TableDef.prototype.check = function (showTrace = false, showError = true) {
_table.checkTableData(this.DATA, showTrace, showError)
}
TableDef.prototype.show = function (show = true, rowCount = -1, colCount = -1, maxColLength = -1) {
_table.showTable(this.DATA, show, rowCount, colCount, maxColLength)
}
/* getValue */
TableDef.prototype.value = function (columnNameOrNo, rowNo = -1, throwError = true) {
return getValue(this.DATA, columnNameOrNo, rowNo, throwError)
}
TableDef.prototype.sort = function (colNameOrNumber, ascending = true) {
_table.sort(this.DATA, colNameOrNumber, ascending)
}
TableDef.prototype.sortColumns = function (colNamesOrNumbers, ascending = true) {
_table.sortColumns(this.DATA, colNamesOrNumbers, ascending)
}
TableDef.prototype.toMD = function (boldColumn = -1, heading = '') {
return _table.table2MD(this.DATA, boldColumn, heading)
}
TableDef.prototype.toCSV = function (addHeadings = true) {
return _table.Table2CSV(this.DATA, addHeadings)
}
TableDef.prototype.toHTML = function () {
return _table.table2HTML(this.DATA)
}
TableDef.prototype.arrayAppend = function (list, id = -1, noId = false) {
_rows.TableArrayAppend(this.DATA, list, id, noId)
}
/* tableFunctions.TableArray2Col */
TableDef.prototype.array2Col = function (list, colName) {
_cols.TableArray2Col(this.DATA, list, colName)
}
TableDef.prototype.array2Cols = function (list) {
_table.TableArray2Columns(list, this)
}
TableDef.prototype.join = function (idMany, tableParent, idParent, columns = [], throwError = true) {
return TableJoin(this.DATA, idMany, tableParent, idParent, columns, throwError)
}
TableDef.prototype.totalCols = function () {
return _table.totalCols(this.DATA)
}
TableDef.prototype.totalRows = function () {
return _table.totalRows(this.DATA)
}
TableDef.prototype.clone = function (columns, tableName = '') {
return cloneTable(this.DATA, columns, tableName)
}
/* cloneTableDropColumn */
TableDef.prototype.cloneDropCol = function (colNameOrNumber) {
return cloneTableDropColumn(this.DATA, colNameOrNumber)
}
/* {{tableFind}} */
TableDef.prototype.find = function (colName, value, findFirst = false) {
return tableFind(this.DATA, colName, value, findFirst)
}
/* filterData */
TableDef.prototype.filter = function (filter, columns = [], show = false, show0Results = true, removeDuplicates = false) {
return filterData(this.DATA, filter, columns, show, show0Results, removeDuplicates)
}
/* Filter */
TableDef.prototype.filterNew = function () {
return new Filter(this.DATA)
}
/* tableMerge */
TableDef.prototype.merge = function (mergeTable, removeDuplicates = false) {
return tableMerge(this.DATA, mergeTable, removeDuplicates)
}
/* tableUnique */
TableDef.prototype.unique = function () {
return tableUnique(this.DATA)
}
/* tableGroupBy */
TableDef.prototype.groupBy = function (groupCols, show = false, sumColumn = '') {
return tableGroupBy(this.DATA, groupCols, show, sumColumn)
}
/* tableStats */
TableDef.prototype.stats = function (show = true, details = false) {
return tableStats(this, show, details)
}
TableDef.prototype.statsDuration = function (fields = [], show = true) {
return durationStats(this.DATA, fields, show)
}
TableDef.prototype.statsDuration2Fields = function (twoFieldArray, show = true) {
return durationStats_From2Fields(this.DATA, twoFieldArray, show)
}
TableDef.prototype.seriesMonth = function (fields = [], show = true) {
return seriesMonth(this.DATA, fields, show)
}
TableDef.prototype.seriesWeek = function (fields = [], show = true) {
return seriesWeek(this.DATA, fields, show)
}
/* {{qaDuplicates}} */
TableDef.prototype.qaDuplicates = function (columns, show = false) {
return qaDuplicates(this.DATA, columns, show)
}
// Exports --------------------------
module.exports = {
TableDef,
TableCheck: _table.TableCheck
}