/
rowset.ts
151 lines (126 loc) · 5.17 KB
/
rowset.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//
// rowset.ts
//
import { SQLCloudRowsetMetadata, SQLiteCloudDataTypes, SQLiteCloudError } from './types'
/** A single row in a dataset with values accessible by column name */
export class SQLiteCloudRow {
constructor(rowset: SQLiteCloudRowset, columnsNames: string[], data: SQLiteCloudDataTypes[]) {
this.#rowset = rowset
this.#data = data
for (let i = 0; i < columnsNames.length; i++) {
this[columnsNames[i]] = data[i]
}
}
// rowset is private
#rowset: SQLiteCloudRowset
// data is private
#data: SQLiteCloudDataTypes[]
/** Returns the rowset that this row belongs to */
// @ts-expect-error
public getRowset(): SQLiteCloudRowset {
return this.#rowset
}
/** Returns rowset data as a plain array of values */
// @ts-expect-error
public getData(): SQLiteCloudDataTypes[] {
return this.#data
}
/** Column values are accessed by column name */
[columnName: string]: SQLiteCloudDataTypes
}
/* A set of rows returned by a query */
export class SQLiteCloudRowset extends Array<SQLiteCloudRow> {
constructor(metadata: SQLCloudRowsetMetadata, data: any[]) {
super(metadata.numberOfRows)
// console.assert(data !== undefined && data.length === metadata.numberOfRows * metadata.numberOfColumns, 'Invalid rowset data')
// console.assert(metadata !== undefined && metadata.columns.length === metadata.numberOfColumns, 'Invalid columns metadata')
this.#metadata = metadata
this.#data = data
// adjust missing column names, duplicate column names, etc.
const columnNames = this.columnsNames
for (let i = 0; i < metadata.numberOfColumns; i++) {
if (!columnNames[i]) {
columnNames[i] = `column_${i}`
}
let j = 0
while (columnNames.findIndex((name, index) => index !== i && name === columnNames[i]) >= 0) {
columnNames[i] = `${columnNames[i]}_${j}`
j++
}
}
for (let i = 0, start = 0; i < metadata.numberOfRows; i++, start += metadata.numberOfColumns) {
this[i] = new SQLiteCloudRow(this, columnNames, data.slice(start, start + metadata.numberOfColumns))
}
}
/** Metadata contains number of rows and columns, column names, types, etc. */
#metadata: SQLCloudRowsetMetadata
/** Actual data organized in rows */
#data: SQLiteCloudDataTypes[]
/**
* Rowset version is 1 for a rowset with simple column names, 2 for extended metadata
* @see https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md
*/
get version(): number {
return this.#metadata.version
}
/** Number of rows in row set */
get numberOfRows(): number {
return this.#metadata.numberOfRows
}
/** Number of columns in row set */
get numberOfColumns(): number {
return this.#metadata.numberOfColumns
}
/** Array of columns names */
get columnsNames(): string[] {
return this.#metadata.columns.map(column => column.name)
}
/** Get rowset metadata */
get metadata(): SQLCloudRowsetMetadata {
return this.#metadata
}
/** Return value of item at given row and column */
getItem(row: number, column: number): any {
if (row < 0 || row >= this.numberOfRows || column < 0 || column >= this.numberOfColumns) {
throw new SQLiteCloudError(
`This rowset has ${this.numberOfColumns} columns by ${this.numberOfRows} rows, requested column ${column} and row ${row} is invalid.`
)
}
return this.#data[row * this.numberOfColumns + column]
}
/** Returns a subset of rows from this rowset */
slice(start?: number, end?: number): SQLiteCloudRow[] {
// validate and apply boundaries
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
start = start === undefined ? 0 : start < 0 ? this.numberOfRows + start : start
start = Math.min(Math.max(start, 0), this.numberOfRows)
end = end === undefined ? this.numberOfRows : end < 0 ? this.numberOfRows + end : end
end = Math.min(Math.max(start, end), this.numberOfRows)
const slicedMetadata = { ...this.#metadata, numberOfRows: end - start }
const slicedData = this.#data.slice(start * this.numberOfColumns, end * this.numberOfColumns)
console.assert(
slicedData && slicedData.length === slicedMetadata.numberOfRows * slicedMetadata.numberOfColumns,
'SQLiteCloudRowset.slice - invalid rowset data'
)
return new SQLiteCloudRowset(slicedMetadata, slicedData)
}
map(fn: (row: SQLiteCloudRow, index: number, rowset: SQLiteCloudRow[]) => any): any[] {
const results: any[] = []
for (let i = 0; i < this.numberOfRows; i++) {
const row = this[i]
results.push(fn(row, i, this))
}
return results
}
/** Returns an instance of SQLiteCloudRowset where rows have been filtered via given callback */
filter(fn: (row: SQLiteCloudRow, index: number, rowset: SQLiteCloudRow[]) => boolean): SQLiteCloudRow[] {
const filteredData: any[] = []
for (let i = 0; i < this.numberOfRows; i++) {
const row = this[i]
if (fn(row, i, this)) {
filteredData.push(...this.#data.slice(i * this.numberOfColumns, (i + 1) * this.numberOfColumns))
}
}
return new SQLiteCloudRowset({ ...this.#metadata, numberOfRows: filteredData.length / this.numberOfColumns }, filteredData)
}
}