node-firebird 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -189,6 +189,48 @@ class Database extends events_1.default.EventEmitter {
189
189
  });
190
190
  return self;
191
191
  }
192
+ /**
193
+ * Bulk-execute `query` once per row via the Firebird 4 batch API
194
+ * (protocol 16+) with all-or-nothing semantics: the batch runs in its
195
+ * own transaction, committed only when every record succeeded and
196
+ * rolled back otherwise. On failure the error of the first failed
197
+ * record is reported, with the full completion attached as
198
+ * err.batchCompletion. Use transaction.executeBatch for partial-success
199
+ * handling.
200
+ */
201
+ executeBatch(query, rows, callback, options) {
202
+ var self = this;
203
+ self.connection.startTransaction(function (err, transaction) {
204
+ if (err) {
205
+ (0, callback_1.doError)(err, callback);
206
+ return;
207
+ }
208
+ transaction.executeBatch(query, rows, function (err, result) {
209
+ if (err) {
210
+ transaction.rollback(function () {
211
+ (0, callback_1.doError)(err, callback);
212
+ });
213
+ return;
214
+ }
215
+ if (!result.success) {
216
+ transaction.rollback(function () {
217
+ var first = result.errors.length ? result.errors[0] : null;
218
+ var batchError = first
219
+ ? first.error
220
+ : new Error('Batch failed for record(s) ' + result.errorRecordNumbers.join(', '));
221
+ batchError.batchCompletion = result;
222
+ (0, callback_1.doError)(batchError, callback);
223
+ });
224
+ return;
225
+ }
226
+ transaction.commit(function (err) {
227
+ if (callback)
228
+ callback(err, result);
229
+ });
230
+ }, options);
231
+ });
232
+ return self;
233
+ }
192
234
  sequentially(query, params, on, callback, options = {}) {
193
235
  if (params instanceof Function) {
194
236
  options = callback;
@@ -273,6 +315,21 @@ class Database extends events_1.default.EventEmitter {
273
315
  drop(callback) {
274
316
  return this.connection.dropDatabase(callback);
275
317
  }
318
+ /**
319
+ * Cancel the operation currently executing on this connection by sending
320
+ * an out-of-band op_cancel (Firebird 2.5+ / protocol 12+). The cancelled
321
+ * operation fails through its own callback with err.gdscode ===
322
+ * GDSCode.CANCELLED. `kind` defaults to fb_cancel_raise; cancellation is
323
+ * per-attachment, not per-statement.
324
+ */
325
+ cancel(kind, callback) {
326
+ if (typeof kind === 'function') {
327
+ callback = kind;
328
+ kind = undefined;
329
+ }
330
+ this.connection.cancelOperation(kind, callback);
331
+ return this;
332
+ }
276
333
  attachEvent(callback) {
277
334
  var self = this;
278
335
  const eventid = self.eventid++;
@@ -391,6 +448,10 @@ class Database extends events_1.default.EventEmitter {
391
448
  var self = this;
392
449
  return (0, callback_1.fromCallback)(function (cb) { self.execute(query, params, cb, options); });
393
450
  }
451
+ executeBatchAsync(query, rows, options) {
452
+ var self = this;
453
+ return (0, callback_1.fromCallback)(function (cb) { self.executeBatch(query, rows, cb, options); });
454
+ }
394
455
  sequentiallyAsync(query, params, on, options) {
395
456
  if (params instanceof Function) {
396
457
  options = on;
@@ -423,6 +484,10 @@ class Database extends events_1.default.EventEmitter {
423
484
  var self = this;
424
485
  return (0, callback_1.fromCallback)(function (cb) { self.attachEvent(cb); });
425
486
  }
487
+ cancelAsync(kind) {
488
+ var self = this;
489
+ return (0, callback_1.fromCallback)(function (cb) { self.cancel(kind, cb); });
490
+ }
426
491
  /**
427
492
  * Run `work` inside a transaction: commits when the returned promise
428
493
  * resolves, rolls back when it rejects (the original error is rethrown,
@@ -42,7 +42,7 @@ export declare class XdrWriter {
42
42
  constructor(size?: number);
43
43
  ensure(len: number): void;
44
44
  addInt(value: number): void;
45
- addInt64(value: number): void;
45
+ addInt64(value: number | bigint): void;
46
46
  addInt128(value: number | bigint | string): void;
47
47
  addDecFloat16(value: number | string | bigint): void;
48
48
  addDecFloat34(value: number | string | bigint): void;
@@ -53,6 +53,7 @@ export declare class XdrWriter {
53
53
  addBlr(blr: BlrWriter): void;
54
54
  getData(): Buffer;
55
55
  addDouble(value: number): void;
56
+ addFloat(value: number): void;
56
57
  addQuad(quad: {
57
58
  low: number;
58
59
  high: number;
@@ -226,10 +226,10 @@ class XdrWriter {
226
226
  }
227
227
  addInt64(value) {
228
228
  this.ensure(8);
229
- // Note: precision is limited to Number.MAX_SAFE_INTEGER (±2^53-1).
230
- // Values outside this range lose precision, which matches the previous
231
- // Long.fromNumber() behaviour.
232
- this.buffer.writeBigInt64BE(BigInt(Math.trunc(value)), this.pos);
229
+ // Note: for numbers, precision is limited to Number.MAX_SAFE_INTEGER
230
+ // (±2^53-1); values outside this range lose precision, which matches
231
+ // the previous Long.fromNumber() behaviour. BigInts keep full precision.
232
+ this.buffer.writeBigInt64BE(typeof value === 'bigint' ? value : BigInt(Math.trunc(value)), this.pos);
233
233
  this.pos += 8;
234
234
  }
235
235
  addInt128(value) {
@@ -306,6 +306,11 @@ class XdrWriter {
306
306
  this.buffer.writeDoubleBE(value, this.pos);
307
307
  this.pos += 8;
308
308
  }
309
+ addFloat(value) {
310
+ this.ensure(4);
311
+ this.buffer.writeFloatBE(value, this.pos);
312
+ this.pos += 4;
313
+ }
309
314
  addQuad(quad) {
310
315
  this.ensure(8);
311
316
  var b = this.buffer;
@@ -21,7 +21,13 @@ declare class Statement {
21
21
  fetch(transaction: any, count: number | string, callback: (err: any, result?: any) => void): void;
22
22
  fetchScroll(transaction: any, direction: string | number, offset?: any, count?: any, callback?: any): void;
23
23
  fetchAll(transaction: any, callback: (err: any, result?: any) => void): void;
24
+ /**
25
+ * Execute this statement once per row via the Firebird 4 batch API
26
+ * (protocol 16+). `rows` is an array of parameter arrays.
27
+ */
28
+ executeBatch(transaction: any, rows: any[][], callback?: any, options?: any): void;
24
29
  executeAsync(transaction: any, params?: any, options?: any): Promise<any>;
30
+ executeBatchAsync(transaction: any, rows: any[][], options?: any): Promise<any>;
25
31
  fetchAsync(transaction: any, count: number | string): Promise<any>;
26
32
  fetchScrollAsync(transaction: any, direction: string | number, offset?: any, count?: any): Promise<any>;
27
33
  fetchAllAsync(transaction: any): Promise<any>;
@@ -49,11 +49,22 @@ class Statement {
49
49
  fetchAll(transaction, callback) {
50
50
  this.connection.fetchAll(this, transaction, callback);
51
51
  }
52
+ /**
53
+ * Execute this statement once per row via the Firebird 4 batch API
54
+ * (protocol 16+). `rows` is an array of parameter arrays.
55
+ */
56
+ executeBatch(transaction, rows, callback, options) {
57
+ this.connection.executeBatch(transaction, this, rows, callback, options);
58
+ }
52
59
  /* Promise / async-await API — wrappers over the callback methods above. */
53
60
  executeAsync(transaction, params, options) {
54
61
  var self = this;
55
62
  return (0, callback_1.fromCallback)(function (cb) { self.execute(transaction, params, cb, options); });
56
63
  }
64
+ executeBatchAsync(transaction, rows, options) {
65
+ var self = this;
66
+ return (0, callback_1.fromCallback)(function (cb) { self.executeBatch(transaction, rows, cb, options); });
67
+ }
57
68
  fetchAsync(transaction, count) {
58
69
  var self = this;
59
70
  return (0, callback_1.fromCallback)(function (cb) { self.fetch(transaction, count, cb); });
@@ -1,8 +1,3 @@
1
- /***************************************
2
- *
3
- * Transaction
4
- *
5
- ***************************************/
6
1
  declare class Transaction {
7
2
  connection: any;
8
3
  db: any;
@@ -13,6 +8,17 @@ declare class Transaction {
13
8
  execute(query: string, params?: any, callback?: any, options?: any): void;
14
9
  sequentially(query: string, params?: any, on?: any, callback?: any, options?: any): this;
15
10
  query(query: string, params?: any, callback?: any, options?: any): void;
11
+ /**
12
+ * Execute `query` once per row in `rows` using the Firebird 4 batch API
13
+ * (protocol 16+, single network flush). The callback receives a
14
+ * completion object: { recordCount, updateCounts, errors:
15
+ * [{recordNumber, error}], errorRecordNumbers, success }. Per-record
16
+ * failures do NOT roll anything back here — inspect the completion and
17
+ * commit or roll back yourself (or use db.executeBatch for
18
+ * all-or-nothing semantics).
19
+ */
20
+ executeBatch(query: string, rows: any[][], callback?: any, options?: any): void;
21
+ executeBatchAsync(query: string, rows: any[][], options?: any): Promise<any>;
16
22
  commit(callback?: (err?: any) => void): void;
17
23
  rollback(callback?: (err?: any) => void): void;
18
24
  commitRetaining(callback?: (err?: any) => void): void;
@@ -10,6 +10,36 @@ const const_1 = __importDefault(require("./const"));
10
10
  * Transaction
11
11
  *
12
12
  ***************************************/
13
+ /** The error delivered when options.signal was already aborted on entry. */
14
+ function abortError(signal) {
15
+ if (signal && signal.reason instanceof Error)
16
+ return signal.reason;
17
+ var err = new Error('The operation was aborted');
18
+ err.name = 'AbortError';
19
+ err.code = 'ABORT_ERR';
20
+ return err;
21
+ }
22
+ /**
23
+ * Wire an AbortSignal to a running statement: on abort, send an out-of-band
24
+ * op_cancel so the server fails the executing operation with isc_cancelled
25
+ * (surfaced through the statement's own callback as err.gdscode ===
26
+ * GDSCode.CANCELLED). Returns the wrapped callback that detaches the
27
+ * listener once the operation settles.
28
+ */
29
+ function hookAbortSignal(connection, signal, callback) {
30
+ var settled = false;
31
+ var onAbort = function () {
32
+ if (!settled)
33
+ connection.cancelOperation(const_1.default.fb_cancel_raise);
34
+ };
35
+ signal.addEventListener('abort', onAbort, { once: true });
36
+ return function (err, result, meta, isSelect) {
37
+ settled = true;
38
+ signal.removeEventListener('abort', onAbort);
39
+ if (callback)
40
+ callback(err, result, meta, isSelect);
41
+ };
42
+ }
13
43
  class Transaction {
14
44
  constructor(connection) {
15
45
  this.connection = connection;
@@ -32,6 +62,14 @@ class Transaction {
32
62
  callback = params;
33
63
  params = undefined;
34
64
  }
65
+ var signal = options && options.signal;
66
+ if (signal) {
67
+ if (signal.aborted) {
68
+ (0, callback_1.doError)(abortError(signal), callback);
69
+ return;
70
+ }
71
+ callback = hookAbortSignal(this.connection, signal, callback);
72
+ }
35
73
  var self = this;
36
74
  this.newStatement(query, function (err, statement) {
37
75
  if (err) {
@@ -162,6 +200,33 @@ class Transaction {
162
200
  };
163
201
  this.execute(query, params, callback, options);
164
202
  }
203
+ /**
204
+ * Execute `query` once per row in `rows` using the Firebird 4 batch API
205
+ * (protocol 16+, single network flush). The callback receives a
206
+ * completion object: { recordCount, updateCounts, errors:
207
+ * [{recordNumber, error}], errorRecordNumbers, success }. Per-record
208
+ * failures do NOT roll anything back here — inspect the completion and
209
+ * commit or roll back yourself (or use db.executeBatch for
210
+ * all-or-nothing semantics).
211
+ */
212
+ executeBatch(query, rows, callback, options) {
213
+ var self = this;
214
+ this.newStatement(query, function (err, statement) {
215
+ if (err) {
216
+ (0, callback_1.doError)(err, callback);
217
+ return;
218
+ }
219
+ statement.executeBatch(self, rows, function (err, result) {
220
+ statement.release();
221
+ if (callback)
222
+ callback(err, result);
223
+ }, options);
224
+ });
225
+ }
226
+ executeBatchAsync(query, rows, options) {
227
+ var self = this;
228
+ return (0, callback_1.fromCallback)(function (cb) { self.executeBatch(query, rows, cb, options); });
229
+ }
165
230
  commit(callback) {
166
231
  this.connection.commit(this, callback);
167
232
  }
@@ -155,6 +155,15 @@ export declare class SQLParamBuffer {
155
155
  encode(data: XdrWriter): void;
156
156
  calcBlr(blr: BlrWriter): void;
157
157
  }
158
+ /**
159
+ * Split a JS Date into the Firebird wire representation used by
160
+ * TIMESTAMP/DATE/TIME columns: `date` is the modified-Julian day number and
161
+ * `time` the count of 100-microsecond units since midnight (local time).
162
+ */
163
+ export declare function encodeDateTimeParts(value: Date): {
164
+ date: number;
165
+ time: number;
166
+ };
158
167
  export declare class SQLParamQuad {
159
168
  value: any;
160
169
  constructor(value: any);
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SQLParamBool = exports.SQLParamDate = exports.SQLParamQuad = exports.SQLParamBuffer = exports.SQLParamString = exports.SQLParamDouble = exports.SQLParamDecFloat34 = exports.SQLParamDecFloat16 = exports.SQLParamInt128 = exports.SQLParamInt64 = exports.SQLParamInt = exports.SQLVarBoolean = exports.SQLVarTimeStampTzEx = exports.SQLVarTimeStampTz = exports.SQLVarTimeTzEx = exports.SQLVarTimeTz = exports.SQLVarTimeStamp = exports.SQLVarTime = exports.SQLVarDate = exports.SQLVarDouble = exports.SQLVarFloat = exports.SQLVarDecFloat34 = exports.SQLVarDecFloat16 = exports.SQLVarInt128 = exports.SQLVarInt64 = exports.SQLVarShort = exports.SQLVarInt = exports.SQLVarArray = exports.SQLVarBlob = exports.SQLVarQuad = exports.SQLVarString = exports.SQLVarNull = exports.SQLVarText = exports.SQLVarBase = void 0;
7
+ exports.encodeDateTimeParts = encodeDateTimeParts;
7
8
  const const_1 = __importDefault(require("./const"));
8
9
  /***************************************
9
10
  *
@@ -593,6 +594,24 @@ class SQLParamBuffer {
593
594
  }
594
595
  exports.SQLParamBuffer = SQLParamBuffer;
595
596
  //------------------------------------------------------
597
+ /**
598
+ * Split a JS Date into the Firebird wire representation used by
599
+ * TIMESTAMP/DATE/TIME columns: `date` is the modified-Julian day number and
600
+ * `time` the count of 100-microsecond units since midnight (local time).
601
+ */
602
+ function encodeDateTimeParts(value) {
603
+ var ms = value.getTime() - value.getTimezoneOffset() * MsPerMinute;
604
+ var time = ms % TimeCoeff;
605
+ var date = (ms - time) / TimeCoeff + DateOffset;
606
+ time *= 10;
607
+ // check overflow (dates before the epoch)
608
+ if (time < 0) {
609
+ date--;
610
+ time = TimeCoeff * 10 + time;
611
+ }
612
+ return { date: date, time: time };
613
+ }
614
+ //------------------------------------------------------
596
615
  class SQLParamQuad {
597
616
  constructor(value) {
598
617
  this.value = value;
@@ -621,17 +640,9 @@ class SQLParamDate {
621
640
  }
622
641
  encode(data) {
623
642
  if (this.value != null) {
624
- var value = this.value.getTime() - this.value.getTimezoneOffset() * MsPerMinute;
625
- var time = value % TimeCoeff;
626
- var date = (value - time) / TimeCoeff + DateOffset;
627
- time *= 10;
628
- // check overflow
629
- if (time < 0) {
630
- date--;
631
- time = TimeCoeff * 10 + time;
632
- }
633
- data.addInt(date);
634
- data.addUInt(time);
643
+ var parts = encodeDateTimeParts(this.value);
644
+ data.addInt(parts.date);
645
+ data.addUInt(parts.time);
635
646
  }
636
647
  else {
637
648
  data.addInt(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-firebird",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
5
5
  "keywords": [
6
6
  "firebird",
package/src/types.ts CHANGED
@@ -75,9 +75,43 @@ export type TransactionOptions = {
75
75
  waitTimeout?: number;
76
76
  };
77
77
 
78
+ /** Result of an executeBatch call (Firebird 4 batch API, protocol 16+). */
79
+ export interface BatchResult {
80
+ /** Total number of records processed by the server. */
81
+ recordCount: number;
82
+ /** Per-record update counts (in record order). */
83
+ updateCounts: number[];
84
+ /** Per-record failures with full status vectors (capped by `detailedErrors`). */
85
+ errors: Array<{ recordNumber: number; error: Error }>;
86
+ /** Record numbers of ALL failed records (detailed + status-less). */
87
+ errorRecordNumbers: number[];
88
+ /** True when every record executed without error. */
89
+ success: boolean;
90
+ }
91
+
92
+ export type BatchOptions = {
93
+ /** Continue past per-record errors and report them all (default true). */
94
+ multiError?: boolean;
95
+ /** Server-side batch buffer limit in bytes (BATCH_TAG_BUFFER_BYTES_SIZE). */
96
+ bufferSize?: number;
97
+ /** Max number of detailed per-record status vectors returned (server default 64). */
98
+ detailedErrors?: number;
99
+ /** Rows per op_batch_msg packet (default 500). */
100
+ chunkSize?: number;
101
+ };
102
+
78
103
  export type QueryOptions = {
79
104
  timeout?: number;
80
105
  scrollable?: boolean;
106
+ /**
107
+ * Abort the query when the signal fires (Firebird 2.5+ / protocol 12+).
108
+ * If the signal is already aborted the query is not sent at all and the
109
+ * callback/promise fails with an `AbortError`. If it fires mid-flight an
110
+ * out-of-band op_cancel is sent and the query fails with
111
+ * `err.gdscode === GDSCode.CANCELLED`. Cancellation is per-attachment:
112
+ * it cancels whatever is currently executing on the connection.
113
+ */
114
+ signal?: AbortSignal;
81
115
  }
82
116
 
83
117
  export interface Database {
@@ -86,6 +120,8 @@ export interface Database {
86
120
  newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): Database;
87
121
  query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
88
122
  execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
123
+ /** Bulk-execute in its own transaction, all-or-nothing (Firebird 4.0+). */
124
+ executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
89
125
  sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
90
126
  drop(callback: SimpleCallback): void;
91
127
  escape(value: any): string;
@@ -99,6 +135,7 @@ export interface Database {
99
135
  // Result metadata is only available through the callback API.
100
136
  queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
101
137
  executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
138
+ executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
102
139
  sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
103
140
  sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
104
141
  transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
@@ -109,12 +146,22 @@ export interface Database {
109
146
  attachEventAsync(): Promise<any>;
110
147
  /** Starts a transaction, commits when `work` resolves, rolls back when it rejects. */
111
148
  withTransaction<T>(work: (transaction: Transaction) => Promise<T> | T, options?: TransactionOptions | Isolation): Promise<T>;
149
+ /**
150
+ * Cancel the operation currently executing on this connection
151
+ * (Firebird 2.5+ / protocol 12+). The cancelled operation fails through
152
+ * its own callback/promise with `err.gdscode === GDSCode.CANCELLED`.
153
+ */
154
+ cancel(callback?: SimpleCallback): Database;
155
+ cancel(kind: number, callback?: SimpleCallback): Database;
156
+ cancelAsync(kind?: number): Promise<void>;
112
157
  }
113
158
 
114
159
  export interface Transaction {
115
160
  newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): void;
116
161
  query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
117
162
  execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
163
+ /** Bulk-execute within this transaction; per-record failures do not roll back (Firebird 4.0+). */
164
+ executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
118
165
  sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
119
166
  commit(callback?: SimpleCallback): void;
120
167
  commitRetaining(callback?: SimpleCallback): void;
@@ -124,6 +171,7 @@ export interface Transaction {
124
171
  // Promise / async-await API
125
172
  queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
126
173
  executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
174
+ executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
127
175
  sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
128
176
  sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
129
177
  newStatementAsync(query: string): Promise<Statement>;
@@ -142,8 +190,12 @@ export interface Statement {
142
190
  fetchScroll(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset: number, count: number, callback: QueryCallback): void;
143
191
  fetchAll(transaction: Transaction, callback: QueryCallback): void;
144
192
 
193
+ /** Execute this prepared statement once per row (Firebird 4.0+ batch API). */
194
+ executeBatch(transaction: Transaction, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
195
+
145
196
  // Promise / async-await API
146
197
  executeAsync(transaction: Transaction, params?: any[], options?: QueryOptions): Promise<any>;
198
+ executeBatchAsync(transaction: Transaction, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
147
199
  fetchAsync(transaction: Transaction, count: number | 'all'): Promise<any>;
148
200
  fetchScrollAsync(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset?: number, count?: number): Promise<any>;
149
201
  fetchAllAsync(transaction: Transaction): Promise<any>;