-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathyottadb.lua
1689 lines (1562 loc) · 76.1 KB
/
yottadb.lua
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- Copyright 2021-2022 Mitchell and Berwyn Hoyt. See LICENSE.
--- Lua-bindings for YottaDB, sponsored by the [University of Antwerp Library](https://www.uantwerpen.be/en/library/).
-- See [README](https://github.com/anet-be/lua-yottadb#overview) for a quick introduction with examples. <br>
-- Home page: [https://github.com/anet-be/lua-yottadb/](https://github.com/anet-be/lua-yottadb/)
-- @module yottadb
local M = {}
local lua_version = tonumber( string.match(_VERSION, " ([0-9]+[.][0-9]+)") )
local _yottadb = require('_yottadb')
for k, v in pairs(_yottadb) do if k:find('^YDB_') then M[k] = v end end
M._VERSION = _yottadb._VERSION
local ydb_release = tonumber( string.match(_yottadb.get('$ZYRELEASE'), "[A-Za-z]+ r([0-9]+[.][0-9]+)") )
-- The following renames documentation section title "Functions" to "Low level functions". Blank line necessary:
--- Basic low level functions
-- @section
--- Block or unblock YDB signals while M code is running.
-- This function is designed to be passed as a callback to yottadb.init() as the `signal_blocker` parameter.
-- The function accepts one boolean parameter: `true` for entering Lua and `false` for exiting Lua:
--
-- * When `true` is passed to this function it blocks all the signals listed in `BLOCKED_SIGNALS` in `callins.c`
-- using OS call `sigprocmask()`. `SIGALRM` is treated specially: instead of being blocked OS call sigaction()
-- is used to set the `SA_RESTART` flag for `SIGALRM`. This makes the OS automatically restart IO calls that are
-- interrupted by `SIGALRM`. The benefit of this over blocking is that the YottaDB `SIGALRM` handler does
-- actually run, even during Lua code, allowing YottaDB to flush the database or IO as necessary (otherwise
-- M code would need to run the M command `VIEW "FLUSH"` after calling Lua code).
-- * When `false` is passed to this function it will reverse the effect, unblocking and clearing `SA_RESTART` for
-- the same signals manipulated when `true` is passed.
--
-- *Note:* This function does take time, as OS calls are slow. Using it will increase the M calling overhead
-- by a factor of 2-5 times the bare calling overhead (on the order of 1.4 microseconds on a typical
-- 2020s x86_64 CPU; see `make benchmarks`)
-- @function block_M_signals
-- @param bool true to block; false to unblock YottaDB signals
-- @return nothing
-- @see init
M.block_M_signals = _yottadb.block_M_signals
--- Initialize ydb and set blocking of M signals.
-- If `signal_blocker` is specified, block M signals which could otherwise interrupt slow IO operations like reading from stdin or a pipe.
-- Raise any errors.
-- See also the notes on signals in the [README](https://github.com/anet-be/lua-yottadb#signals--eintr-errors).
--
-- *Note:* any calls to the YDB API also initialize YDB; any subsequent call here will set `signal_blocker` but not re-init YDB.
-- @function init
-- @param[opt] signal_blocker Specifies a Lua callback CFunction (e.g. `yottadb.block_M_signals()`) which will be
-- called with its one parameter set to false on entry to M, and with true on exit from M, so as to unblock YDB signals while M is in use.
-- Setting `signal_blocker` to `nil` switches off signal blocking.
--
-- *Note:* Changing this to support a generic Lua function as callback would be possible but slow, as it would require
-- fetching the function pointer from a C closure, and using `lua_call()`.
-- @return nothing
-- @see block_M_signals
M.init = _yottadb.init
--- Lua function to call `ydb_eintr_handler()`.
-- Code intended to handle EINTR errors, instead of blocking signals, should call `ydb_eintr_handler()` when it gets an EINTR return code,
-- before re-issuing the interrupted system call.
-- @function ydb_eintr_handler
-- @return YDB_OK on success, and greater than zero on error (with message in ZSTATUS)
-- @see block_M_signals
M.ydb_eintr_handler = _yottadb.ydb_eintr_handler
-- Valid type tables which may be passed to assert_type() below
local _number_boolean = {number=true, boolean=true}
local _number_nil = {number=true, ['nil']=true}
local _string_number = {string=true, number=true}
local _string_number_nil = {string=true, number=true, ['nil']=true}
local _table_nil = {table=true, ['nil']=true}
local _table_number_nil = {table=true, number=true, ['nil']=true}
local _table_boolean_nil = {table=true, boolean=true, ['nil']=true}
local _table_string = {table=true, string=true}
local _table_string_number = {table=true, string=true, number=true}
local _table_string_number_nil = {table=true, string=true, number=true, ['nil']=true}
local _function_nil = {['function']=true, ['nil']=true}
--- Verify type of v.
-- Asserts that type(`v`) is equal to expected_types (string) or is in `expected_types` (table)
-- and returns `v`, or calls `error()` with an error message that implicates function argument
-- number `narg`.
-- Used with API function arguments so users receive more helpful error messages.
-- @invocation assert_type(value, _string_nil, 1)
-- @invocation assert_type(option.setting, 'number', 'setting') -- implicates key
-- @param v Value to assert the type of.
-- @param expected_types String of valid type or Table whose keys are valid type names to check against.
-- @param narg Integer positional argument number `v` is associated with. This is not required to be a number.
-- @param[opt] funcname String override the name of the calling function (useful for object methods which cannot be auto-detected)
local function assert_type(v, expected_types, narg, funcname)
local t = type(v)
if t == expected_types or expected_types[t] then return v end
-- Note: do not use assert for performance reasons.
if not _table_string[type(expected_types)] then
error(string.format("bad argument #2 to '%s' (table/string expected, got %s)",
debug.getinfo(1, 'n').name, type(expected_types)), 2)
elseif narg == nil then
error(string.format("bad argument #3 to '%s' (value expected, got %s)",
debug.getinfo(1, 'n').name, type(narg)), 2)
end
local expected_list = {}
if type(expected_types) == 'string' then
table.insert(expected_list, expected_types)
else
for t in pairs(expected_types) do table.insert(expected_list, t) end
end
error(string.format("bad argument #%s to '%s' (%s expected, got %s)", narg,
debug.getinfo(2, 'n').name or funcname or '?', table.concat(expected_list, '/'), type(v)), 3)
end
--- Asserts that all items in table *t* are strings.
-- On failuare, calls `error()` with an error message
-- that implicates function argument number *narg* named *name*.
-- Like `assert_type()`, this is intended for improved readability of API input errors.
-- @invocation assert_strings(subsarray, 'subsarray', 2)
-- @param t Table to check. If it is not a table, the assertion passes.
-- @param name String argument name to use in error messages.
-- @param narg The position argument number *t* is associated with.
-- @see assert_type
local function assert_strings(t, name, narg)
if type(t) ~= 'table' then return t end
for i, v in ipairs(t) do
if type(v) ~= 'string' then
error(string.format("bad argument #%s to '%s' (string expected at index %s, got %s)", narg, debug.getinfo(2, 'n').name or '?', i, type(v)), 3)
end
end
return t
end
local function rawtostring(value)
local setmeta = type(value)=='userdata' and _yottadb.cachearray_setmetatable or setmetatable
local metatable = getmetatable(value)
setmeta(value, nil)
local result = tostring(value)
setmeta(value, metatable)
return result
end
-- ~~~ Support for Lua versions prior to 5.4 ~~~
--- Create isinteger(n) that also works in Lua < 5.3 but is fast in Lua >=5.3.
-- @function isinteger
-- @param n number to test
-- @local
local isinteger
if lua_version >= 5.3 then
function isinteger(n)
return math.type(n) == 'integer'
end
else
function isinteger(n)
return type(n) == 'number' and n % 1 == 0
end
end
--- Convert str to number if the lua representation is identical.
-- Otherwise return `nil`
-- @param str string for potential conversion to a number
-- @return number or `nil`
local function q_number(str)
local num = tonumber(str)
return num and tostring(num)==str and num or nil
end
--- Get the YDB error code (if any) contained in the given error message.
-- @param message String error message.
-- @return the YDB error code (if any) for the given error message,
-- @return or `nil` if the message is not a YDB error.
-- @example
-- ydb = require('yottadb')
-- ydb.get_error_code('YDB Error: -150374122: %YDB-E-ZGBLDIRACC, Cannot access global directory !AD!AD!AD.')
-- -- -150374122
function M.get_error_code(message)
return tonumber(assert_type(message, 'string', 1):match('YDB Error: (%-?%d+):'))
end
-- Class metatable for an object that represents a YDB node.
local node = {}
-- Deprecated object that represents a YDB node.
-- old name of YDB node object -- retains the deprecated syntax for backward compatibility
--- @deprecated v1.0
local key = {}
--- Return whether a node has a value or subtree.
-- @function data
-- @invocation yottadb.data('varname'[, {subsarray}][, ...])
-- @invocation yottadb.data(cachearray)
-- @param varname String of the database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts to append after any elements in optional subsarray table
-- @return 0: (node has neither value nor subtree)
-- @return 1: node has value, not subtree
-- @return 10: node has no value, but does have a subtree
-- @return 11: node has both value and subtree
-- @example
-- -- include setup from example at yottadb.set()
-- ydb.data('^Population')
-- -- 10.0
-- ydb.data('^Population', {'USA'})
-- -- 11.0
M.data = _yottadb.data
--- Deprecated and replaced by `set(varname[, subsarray[, ...]], nil)`.
-- @function delete_node
-- @invocation yottadb.delete_node('varname'[, {subsarray}][, ...])
-- @invocation yottadb.delete_node(cachearray)
-- @param varname String of the database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts to append after any elements in optional subsarray table
--- @deprecated v3.0
-- @example
-- ydb = require('yottadb')
-- ydb.set('^Population', {'Belgium'}, 1367000)
-- ydb.delete_node('^Population', {'Belgium'}) -- or ydb.set('^Population', {'Belgium'}, nil)
-- ydb.get('^Population', {'Belgium'})
-- -- nil
M.delete_node = _yottadb.delete
--- Deletes a database variable tree (node and subnodes) or a node subtree.
-- @function kill
-- @invocation yottadb.kill('varname'[, {subsarray}][, ...])
-- @invocation yottadb.kill(cachearray)
-- @param varname String of the database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts to append after any elements in optional subsarray table
-- @example
-- -- include setup from example at yottadb.set()
-- ydb.get('^Population', {'USA'})
-- -- 325737000
-- ydb.get('^Population', {'USA', '17900802'})
-- -- 3929326
-- ydb.get('^Population', {'USA', '18000804'})
-- -- 5308483
-- ydb.kill('^Population', {'USA'})
-- ydb.data('^Population', {'USA'})
-- -- 0.0
function M.kill(varname, ...)
return _yottadb.delete(varname, ..., true)
end
--- Deprecated and replaced by kill.
-- @function delete_tree
-- @param varname String of the database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts to append after any elements in optional subsarray table
-- @see kill
--- @deprecated v3.0
M.delete_tree = M.kill
--- Gets and returns the value of a database variable or node; or `nil` if the variable or node does not exist.
-- @function get
-- @invocation yottadb.get('varname'[, {subsarray}][, ...])
-- @invocation yottadb.get(cachearray)
-- @param varname String of the database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts or table subscripts
-- @return string value or `nil`
-- @example
-- -- include setup from example at yottadb.set()
-- ydb.get('^Population')
-- -- nil
-- ydb.get('^Population', {'Belgium'})
-- -- 1367000
-- ydb.get('$zgbldir')
-- -- /home/ydbuser/.yottadb/r1.34_x86_64/g/yottadb.gld
M.get = _yottadb.get
--- Increments the numeric value of a database variable or node.
-- Raises an error on overflow.
--
-- *Caution:* increment is *not* optional if `...` list of subscript is provided.
-- Otherwise incr() cannot tell whether last parameter is a subscript or an increment.
-- @function incr
-- @invocation yottadb.incr(varname[, {subs}][, increment=1])
-- @invocation yottadb.incr(varname[, {subs}], ..., increment=1)
-- @invocation yottadb.incr(cachearray[, increment=1])
-- @param varname of database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts or table subscripts
-- @param increment Number or string amount to increment by (default=1)
-- @return the new value
-- @example
-- ydb = require('yottadb')
-- ydb.get('num')
-- -- 4
-- ydb.incr('num', 3)
-- -- 7
-- ydb.incr('num')
-- -- 8
M.incr = _yottadb.incr
--- Releases all locks held and attempts to acquire all requested locks.
-- Returns after `timeout`, if specified.
-- If timeout is not supplied or is `nil`, wait forever; timeout of zero means try only once.
-- Raises an error `yottadb.YDB_LOCK_TIMEOUT` if a lock could not be acquired.
-- @param[opt] nodes Table array of {varname[, subs]} elements or node objects that specify the lock names to lock.
-- @param[opt] timeout Number timeout in seconds to wait for the lock.
-- @return 0 (always)
function M.lock(nodes, timeout)
if not timeout then
assert_type(nodes, _table_number_nil, 1)
else
assert_type(nodes, 'table', 1)
assert_type(timeout, 'number', 2)
end
local nodes_copy = {}
if type(nodes) == 'table' then
for i, v in ipairs(nodes) do
assert_type(v, 'table', 1)
local node = v
if type(node) ~= 'userdata' then node = cachearray_create(table.unpack(v)) end
nodes_copy[i] = node
end
end
_yottadb.lock(nodes_copy, timeout)
end
--- Attempts to acquire or increment a lock named varname[(subsarray)].
-- Returns after `timeout`, if specified.
-- Raises a `yottadb.YDB_LOCK_TIMEOUT` error if lock could not be acquired.
--
-- If timeout is not supplied or is `nil`, wait forever; timeout of zero means try only once.
-- *Caution:* timeout is *not* optional if `...` list of subscripts is provided, but it may be `nil`.
-- Otherwise lock_incr cannot tell whether it is a subscript or a timeout.
-- @function lock_incr
-- @invocation yottadb.lock_incr(varname[, {subs}][, ...][, timeout=0])
-- @invocation yottadb.lock_incr(varname[, {subs}], ..., timeout=0)
-- @invocation yottadb.lock_incr(cachearray[, timeout=0])
-- @param varname of database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts or table subscripts
-- @param[opt] timeout Number timeout in seconds to wait for the lock.
-- Optional only if subscripts is a table.
-- @return 0 (always)
-- @see grab
-- @see lock_decr
M.lock_incr = _yottadb.lock_incr
--- Alias for lock_incr to acquire or increment a lock named varname[(subsarray)].
-- Returns after `timeout`, if specified.
-- Raises a `yottadb.YDB_LOCK_TIMEOUT` error if lock could not be acquired.
--
-- If timeout is not supplied or is `nil`, wait forever; timeout of zero means try only once.
-- *Caution:* timeout is *not* optional if `...` list of subscripts is provided, but it may be `nil`.
-- Otherwise lock_incr cannot tell whether it is a subscript or a timeout.
-- @function grab
-- @invocation yottadb.grab(varname[, {subs}][, ...][, timeout=0])
-- @invocation yottadb.grab(varname[, {subs}], ..., timeout=0)
-- @invocation yottadb.grab(cachearray[, timeout=0])
-- @param varname of database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts or table subscripts
-- @param[opt] timeout Number timeout in seconds to wait for the lock.
-- Optional only if subscripts is a table.
-- @return 0 (always)
-- @see lock_incr
-- @see release
M.grab = M.lock_incr
--- Decrements a lock of the same name as varname[(subsarray)], releasing it if zero.
-- Releasing a lock cannot create an error unless the varname/subsarray names are invalid.
-- @function lock_decr
-- @invocation yottadb.lock_decr('varname'[, {subsarray}][, ...])
-- @invocation yottadb.lock_decr(cachearray)
-- @param varname String of the database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts to append after any elements in optional subsarray table
-- @return 0 (always)
-- @see release
-- @see lock_incr
M.lock_decr = _yottadb.lock_decr
--- Alias for lock_decr to decrement a lock of the same name as varname[(subsarray)], releasing it if zero.
-- Releasing a lock cannot create an error unless the varname/subsarray names are invalid.
-- @function release
-- @invocation yottadb.release('varname'[, {subsarray}][, ...])
-- @invocation yottadb.release(cachearray)
-- @param varname String of the database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts to append after any elements in optional subsarray table
-- @return 0 (always)
-- @see lock_decr
-- @see grab
M.release = M.lock_decr
--- Returns the full subscript list of the next node after a database variable or node.
-- A next node chain started from varname will eventually reach all nodes under that varname in order.
--
-- *Note:* `node:gettree()` or `node:subscripts()` may be a better way to iterate a node tree
-- @function node_next
-- @invocation yottadb.node_next('varname'[, {subsarray}][, ...])
-- @invocation yottadb.node_next(cachearray)
-- @param varname String of the database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts to append after any elements in optional subsarray table
-- @return list of subscripts for the node, or `nil` if there isn't a next node
-- @example
-- -- include setup from example at yottadb.set()
-- print(table.concat(ydb.node_next('^Population'), ', '))
-- -- Belgium
-- print(table.concat(ydb.node_next('^Population', {'Belgium'}), ', '))
-- -- Thailand
-- print(table.concat(ydb.node_next('^Population', {'Thailand'}), ', '))
-- -- USA
-- print(table.concat(ydb.node_next('^Population', {'USA'}), ', '))
-- -- USA, 17900802
-- print(table.concat(ydb.node_next('^Population', {'USA', '17900802'}), ', '))
-- -- USA, 18000804
-- @example
-- -- Note: The format used above to print the next node will give an error if there is no next node, i.e., the value returned is nil.
-- -- This case will have to be handled gracefully. The following code snippet is one way to handle nil as the return value:
--
-- local ydb = require('yottadb')
-- next = ydb.node_next('^Population', {'USA', '18000804'})
-- if next ~= nil then
-- print(table.concat(next, ', '))
-- else
-- print(next)
-- end
M.node_next = _yottadb.node_next
--- Returns the full subscript list of the previous node after a database variable or node.
-- A previous node chain started from varname will eventually reach all nodes under that varname in reverse order.
--
-- *Note:* `node:gettree()` or `node:subscripts()` may be a better way to iterate a node tree
-- @function node_previous
-- @invocation yottadb.node_previous('varname'[, {subsarray}][, ...])
-- @invocation yottadb.node_previous(cachearray)
-- @param varname String of the database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts to append after any elements in optional subsarray table
-- @return list of subscripts for the node, or `nil` if there isn't a previous node
-- @example
-- -- include setup from example at yottadb.set()
-- print(table.concat(ydb.node_previous('^Population', {'USA', '18000804'}), ', '))
-- -- USA, 17900802
-- print(table.concat(ydb.node_previous('^Population', {'USA', '17900802'}), ', '))
-- -- USA
-- print(table.concat(ydb.node_previous('^Population', {'USA'}), ', '))
-- -- Thailand
-- print(table.concat(ydb.node_previous('^Population', {'Thailand'}), ', '))
-- -- Belgium
--
-- @example -- Note: See the note on handling nil return values in node_next() which applies to node_previous() as well.
M.node_previous = _yottadb.node_previous
--- Sets the value of a database variable or node.
-- @function set
-- @invocation yottadb.set(varname[, {subs}][, ...], value)
-- @invocation yottadb.set(cachearray, value)
-- @param varname of database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts or table subscripts
-- @param value The value to assign to the node. If this is a number, it is converted to a string. If it is `nil`, the node's value, if any, is deleted.
-- @return `value`
-- @example
-- ydb = require('yottadb')
-- ydb.set('^Population', {'Belgium'}, 1367000)
-- ydb.set('^Population', {'Thailand'}, 8414000)
-- ydb.set('^Population', {'USA'}, 325737000)
-- ydb.set('^Population', {'USA', '17900802'}, 3929326)
-- ydb.set('^Population', {'USA', '18000804'}, 5308483)
M.set = _yottadb.set
--- Returns the next subscript for a database variable or node; or `nil` if there isn't one.
-- @function subscript_next
-- @invocation yottadb.subscript_next(varname[, {subsarray}][, ...])
-- @invocation yottadb.subscript_next(cachearray)
-- @param varname of database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts or table subscripts
-- @return string subscript name, or `nil` if there are no more subscripts
-- @example
-- -- include setup from example at yottadb.set()
-- ydb.subscript_next('^Population', {''})
-- -- Belgium
-- ydb.subscript_next('^Population', {'Belgium'})
-- -- Thailand
-- ydb.subscript_next('^Population', {'Thailand'})
-- -- USA
M.subscript_next = _yottadb.subscript_next
--- Returns the previous subscript for a database variable or node; or `nil` if there isn't one.
-- @function subscript_previous
-- @invocation yottadb.subscript_previous(varname[, {subsarray}][, ...])
-- @invocation yottadb.subscript_previous(cachearray)
-- @param varname of database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts or table subscripts
-- @return string subscript name, or `nil` if there are no previous subscripts
-- @example
-- -- include setup from example at yottadb.set()
-- ydb.subscript_previous('^Population', {'USA', ''})
-- -- 18000804
-- ydb.subscript_previous('^Population', {'USA', '18000804'})
-- -- 17900802
-- ydb.subscript_previous('^Population', {'USA', '17900802'})
-- -- nil
-- ydb.subscript_previous('^Population', {'USA'})
-- -- Thailand
M.subscript_previous = _yottadb.subscript_previous
--- Returns an iterator for iterating over database *sibling* subscripts starting from the node referenced by `varname` and `subarray`.
--
-- *Note:* this starts from the given location and gives the next *sibling* subscript in the M collation sequence.
-- It operates differently than `node:subscipts()` which yields all subscripts that are *children* of the given node,
-- and which you may consider to be preferable.
-- @invocation for name in yottadb.subscripts(varname[, {subsarray}][, ...][, reverse]) do ... end
-- @invocation for name in yottadb.subscripts(cachearray[, ...][, reverse]) do ... end
-- @param varname of database node (this can also be replaced by cachearray)
-- @param[opt] subsarray Table of subscripts
-- @param[opt] ... List of subscripts or table subscripts
-- @param[opt] reverse Flag that indicates whether to iterate backwards. Not optional when '...' is provided
-- @return iterator
function M.subscripts(varname, ...)
local subsarray, reverse
-- First do parameter checking
if ... then
if type(...)=='table' then
subsarray, reverse = ...
else
-- Check & error if last parameter is nil (before turning into a table, which loses the last element)
assert_type(select(-1, ...), _number_boolean, '<last>')
subsarray = {...}
reverse = table.remove(subsarray) -- pop
end
else
subsarray = {}
end
-- Convert to mutable cachearray
local cachearray = _yottadb.cachearray_create(varname, table.unpack(subsarray)) -- works even if varname is already a cachearray
_yottadb.cachearray_setmetatable(cachearray, node) -- make it a node type. Not strictly necessary since code below doesn't call node methods, but in case it does in future ...
cachearray = _yottadb.cachearray_tomutable(cachearray)
local actuator = reverse and _yottadb.subscript_previous or _yottadb.subscript_next
local function iterator()
local next_or_prev = actuator(cachearray)
cachearray = _yottadb.cachearray_subst(cachearray, next_or_prev or '')
return next_or_prev
end
return iterator, nil, '' -- iterate using child from ''
end
--- Returns the zwrite-formatted version of the given string.
-- @function str2zwr
-- @param s String to format.
-- @return formatted string
-- @example
-- ydb=require('yottadb')
-- str='The quick brown dog\b\b\bfox jumps over the lazy fox\b\b\bdog.'
-- print(str)
-- -- The quick brown fox jumps over the lazy dog.
-- ydb.str2zwr(str)
-- -- "The quick brown dog"_$C(8,8,8)_"fox jumps over the lazy fox"_$C(8,8,8)_"dog."
M.str2zwr = _yottadb.str2zwr
--- Returns the string described by the given zwrite-formatted string.
-- @function zwr2str
-- @param s String in zwrite format.
-- @return string
-- @example
-- ydb=require('yottadb')
-- str1='The quick brown dog\b\b\bfox jumps over the lazy fox\b\b\bdog.'
-- zwr_str=ydb.str2zwr(str1)
-- print(zwr_str)
-- -- "The quick brown dog"_$C(8,8,8)_"fox jumps over the lazy fox"_$C(8,8,8)_"dog."
-- str2=ydb.zwr2str(zwr_str)
-- print(str2)
-- -- The quick brown fox jumps over the lazy dog.
-- str1==str2
-- -- true
M.zwr2str = _yottadb.zwr2str
-- The following 6 lines defines the 'High level functions' section to appear
-- before 'Transactions' in the docs. The blank lines in between are necessary:
--- @section end
--- Transactions
-- @section
--- Initiates a transaction (low level function).
-- Restarts are subject to `$ZMAXTPTIME` after which they cause error `%YDB-E-TPTIMEOUT`
-- @param[opt] id optional string transaction id. For special ids `BA` or `BATCH`, see [Transaction Processing](https://docs.yottadb.com/ProgrammersGuide/langfeat.html#transaction-processing).
-- @param[opt] varnames optional table of local M variable names to restore on transaction restart
-- (or `{'*'}` for all locals)
-- Restoration applies to rollback.
-- @param f Function to call. The transaction's affected globals are:
--
-- * Committed if the function returns nothing or `yottadb.YDB_OK`.
-- * Restarted if the function returns `yottadb.YDB_TP_RESTART` (`f` will be called again).
-- * Not committed if the function returns `yottadb.YDB_TP_ROLLBACK` or errors out.
-- @param[opt] ... arguments to pass to `f`
-- @see transaction
-- @example
-- local ydb = require('yottadb')
--
-- function transfer_to_savings(t)
-- local ok, e = pcall(ydb.incr, '^checking', -t)
-- if (ydb.get_error_code(e) == ydb.YDB_TP_RESTART) then
-- return ydb.YDB_TP_RESTART
-- end
-- if (not ok or tonumber(e)<0) then
-- return ydb.YDB_TP_ROLLBACK
-- end
-- local ok, e = pcall(ydb.incr, '^savings', t)
-- if (ydb.get_error_code(e) == ydb.YDB_TP_RESTART) then
-- return ydb.YDB_TP_RESTART
-- end
-- if (not ok) then
-- return ydb.YDB_TP_ROLLBACK
-- end
-- return ydb.YDB_OK
-- end
--
-- ydb.set('^checking', 200)
-- ydb.set('^savings', 85000)
--
-- print("Amount currently in checking account: $" .. ydb.get('^checking'))
-- print("Amount currently in savings account: $" .. ydb.get('^savings'))
--
-- print("Transferring $10 from checking to savings")
-- local ok, e = pcall(ydb.tp, '', {'*'}, transfer_to_savings, 10)
-- if (not e) then
-- print("Transfer successful")
-- elseif (ydb.get_error_code(e) == ydb.YDB_TP_ROLLBACK) then
-- print("Transfer not possible. Insufficient funds")
-- end
--
-- print("Amount in checking account: $" .. ydb.get('^checking'))
-- print("Amount in savings account: $" .. ydb.get('^savings'))
--
-- print("Transferring $1000 from checking to savings")
-- local ok, e = pcall(ydb.tp, '', {'*'}, transfer_to_savings, 1000)
-- if (not e) then
-- print("Transfer successful")
-- elseif (ydb.get_error_code(e) == ydb.YDB_TP_ROLLBACK) then
-- print("Transfer not possible. Insufficient funds")
-- end
--
-- print("Amount in checking account: $" .. ydb.get('^checking'))
-- print("Amount in savings account: $" .. ydb.get('^savings'))
-- @example
-- Output:
-- Amount currently in checking account: $200
-- Amount currently in savings account: $85000
-- Transferring $10 from checking to savings
-- Transfer successful
-- Amount in checking account: $190
-- Amount in savings account: $85010
-- Transferring $1000 from checking to savings
-- Transfer not possible. Insufficient funds
-- Amount in checking account: $190
-- Amount in savings account: $85010
function M.tp(id, varnames, f, ...)
-- fill in missing inputs if necessary
local args={id, varnames, f, ...}
local inserted = 0
if type(args[1]) ~= 'string' then table.insert(args, 1, "") inserted = inserted+1 end
if type(args[2]) ~= 'table' then table.insert(args, 2, {}) inserted = inserted+1 end
id, varnames, f = args[1], args[2], args[3]
assert_type(id, 'string', 1)
assert_type(varnames, 'table', 2-inserted)
assert_strings(varnames, 'varnames', 2-inserted)
assert_type(f, 'function', 3-inserted)
_yottadb.tp(id, varnames, f, table.unpack(args, 4))
end
--- Returns a high-level transaction-safe version of the given function.
-- It will be called within a YottaDB transaction and the database globals restored on error or `yottadb.trollback()`
-- @param[opt] id optional string transaction id. For special ids `BA` or `BATCH`, see [Transaction Processing](https://docs.yottadb.com/ProgrammersGuide/langfeat.html#transaction-processing).
-- @param[opt] varnames optional table of local M variable names to restore on transaction `trestart()`
-- (or `{'*'}` for all locals). Restoration applies to rollback.
-- @param f Function to call. The transaction's affected globals are:
--
-- * Committed if the function returns nothing or `yottadb.YDB_OK`.
-- * Restarted if the function returns `yottadb.YDB_TP_RESTART` (`f` will be called again).
-- Restarts are subject to `$ZMAXTPTIME` after which they cause error `%YDB-E-TPTIMEOUT`
-- * Not committed if the function returns `yottadb.YDB_TP_ROLLBACK` or errors out.
-- @return transaction-safe function.
-- @see tp
-- @example
-- Znode = ydb.node('^Ztest')
-- transact = ydb.transaction(function(end_func)
-- print("^Ztest starts as", Znode:get())
-- Znode:set('value')
-- end_func()
-- end)
--
-- transact(ydb.trollback) -- perform a rollback after setting Znode
-- -- ^Ztest starts as nil
-- -- YDB Error: 2147483645: YDB_TP_ROLLBACK
-- -- stack traceback:
-- -- [C]: in function '_yottadb.tp' ...
-- Znode:get() -- see that the data didn't get set
-- -- nil
--
-- tries = 2
-- function trier() tries=tries-1 if tries>0 then ydb.trestart() end end
-- transact(trier) -- restart with initial dbase state and try again
-- -- ^Ztest starts as nil
-- -- ^Ztest starts as nil
-- Znode:get() -- check that the data got set after restart
-- -- value
--
-- Znode:set(nil)
-- transact(function() end) -- end the transaction normally without restart
-- -- ^Ztest starts as nil
-- Znode:get() -- check that the data got set
-- -- value
function M.transaction(id, varnames, f)
-- fill in missing inputs if necessary
if type(id) ~= 'string' then id, varnames, f = "", id, varnames end
if type(varnames) ~= 'table' then varnames, f = {}, varnames end
return function(...)
local args = {...}
local function wrapped_transaction()
local ok, result = pcall(f, table.unpack(args))
if ok and not result then
result = _yottadb.YDB_OK
elseif not ok and M.get_error_code(result) == _yottadb.YDB_TP_RESTART then
result = _yottadb.YDB_TP_RESTART
elseif not ok and M.get_error_code(result) == _yottadb.YDB_TP_ROLLBACK then
result = _yottadb.YDB_TP_ROLLBACK
elseif not ok then
error(result, 2)
end
return result
end
return _yottadb.tp(id, varnames, wrapped_transaction, ...)
end
end
--- Make the currently running transaction function restart immediately.
function M.trestart()
error(_yottadb.message(_yottadb.YDB_TP_RESTART))
end
--- Make the currently running transaction function rollback immediately with a YDB_TP_ROLLBACK error.
function M.trollback()
error(_yottadb.message(_yottadb.YDB_TP_ROLLBACK))
end
--- @section end
-- ~~~ Functions to handle calling M routines from Lua ~~~
--- High level functions
-- @section
local param_type_enums = _yottadb.YDB_CI_PARAM_TYPES
string_pack = string.pack or _yottadb.string_pack -- string.pack only available from Lua 5.3
if lua_version < 5.2 then
string.format = _yottadb.string_format -- make it also print tables in Lua 5.1
table.unpack = _yottadb.table_unpack
end
--- Parse ydb call-in type of the typical form: IO:ydb_int_t*.
-- Spaces must be removed before calling this function.
-- Asserts any errors.
-- @return a string of type info packed into a string matching callins.h struct type_spec
local function pack_type(type_str, param_id)
local pattern = '(I?)(O?):([%w_]+%*?)(.*)'
local i, o, typ, alloc_str = type_str:match(pattern)
assert(typ, string.format("ydb parameter %s not found in YDB call-in table specification %q", param_id, type_str))
local type_enum = param_type_enums[typ]
assert(type_enum, string.format("unknown parameter %s '%s' in YDB call-in table specification", param_id, typ))
local input = i:upper()=='I' and 1 or 0
local output = o:upper()=='O' and 1 or 0
assert(output==0 or typ:find('*', 1, true), string.format("call-in output parameter %s '%s' must be a pointer type", param_id, typ))
local alloc_str = alloc_str:match('%[(%d+)%]')
local preallocation = tonumber(alloc_str) or -1
assert(preallocation==-1 or type_enum>0x80, string.format("preallocation is meaningless for number type call-in parameter %s: '%s'", param_id, typ))
assert(preallocation==-1 or output==1, string.format("preallocation is pointless for input-only parameter %s: '%s'", param_id, typ))
assert(preallocation==-1 or typ~='ydb_char_t*', string.format("preallocation for ydb_char_t* output parameter %s is forced to [%d] to prevent overruns", param_id, _yottadb.YDB_MAX_STR))
return string_pack('!=TBBBXT', preallocation, type_enum, input, output)
end
--- Parse one-line string of the ydb call-in file format.
-- Raise any errors.
-- @param line is the text in the current line of the call-in file
-- @param ci_handle handle of call-in table
-- @return C name for the M routine
-- @return a function to invoke the M routine
-- @return or return `nil` if the line did not contain a function prototype
local function parse_prototype(line, ci_handle)
line = line:gsub('//.*', '') -- remove comments
-- example prototype line: test_Run: ydb_string_t* %Run^test(I:ydb_string_t*, I:ydb_int_t, I:ydb_int_t)
local pattern = '%s*([^:%s]+)%s*:%s*([%w_]+%s*%*?[%s%[%]%d]*)%s*([^(%s]+)%s*%(([^)]*)%)'
local routine_name, ret_type, entrypoint, params = line:match(pattern)
if not routine_name then return nil, nil end
assert(params, string.format("Line does not match YDB call-in table specification: '%s'", line))
local ret_type = ret_type:gsub('%s*', '') -- remove spaces
local param_info = {pack_type(ret_type=='void' and ':void' or 'O:'..ret_type, "'return_value'")}
-- now iterate each parameter
params = params:gsub('%s*', '') -- remove spaces
local i = 0
for type_str in params:gmatch('([^,%)]+)') do
i = i+1
table.insert(param_info, pack_type(type_str, i))
end
local param_info_string = table.concat(param_info)
local routine_name_handle = _yottadb.register_routine(routine_name, entrypoint)
-- create a table used by func() below to reference the routine_name string to ensure it isn't garbage collected
-- because it's used by C userdata in 'routine_name_handle', but and not referenced by Lua func()
local routine_name_table = {routine_name_handle, routine_name}
-- create the actual wrapper function
local function func(...)
return _yottadb.cip(ci_handle, routine_name_table[1], param_info_string, ...)
end
return routine_name, func
end
--- Import M routines as Lua functions specified in a ydb 'call-in' file. <br>
-- See example call-in file [arithmetic.ci](https://github.com/anet-be/lua-yottadb/blob/master/examples/arithmetic.ci)
-- and matching M file [arithmetic.m](https://github.com/anet-be/lua-yottadb/blob/master/examples/arithmetic.m).
-- @param Mprototypes A list of lines in the format of ydb 'call-in' files required by `ydb_ci()`.
-- If the string contains `:` it is considered to be the call-in specification itself;
-- otherwise it is treated as the filename of a call-in file to be opened and read.
-- @param debug When neither false nor nil, tell Lua not to delete the temporary preprocessed call-in table file it created.
-- The name of this file will be stored in the `__ci_filename` field of the returned table.
-- @return A table of functions analogous to a Lua module.
-- Each function in the table will call an M routine specified in `Mprototypes`.
-- @example
-- $ export ydb_routines=examples # put arithmetic.m into ydb path
-- $ lua -lyottadb
-- arithmetic = yottadb.require('examples/arithmetic.ci')
-- arithmetic.add_verbose("Sum is:", 2, 3)
-- -- Sum is: 5
-- -- Sum is: 5
-- arithmetic.sub(5,7)
-- -- -2
function M.require(Mprototypes,debug)
local routines = {}
if not Mprototypes:find(':', 1, true) then
-- read call-in file
routines.__ci_filename_original = Mprototypes
local f = assert(io.open(Mprototypes))
Mprototypes = f:read('a')
f:close()
end
-- now preprocess the call-in table to make it more suited to lua-yottadb
-- first, declare a function that will be passed every string of types, for potential substitution
local function type_replacer(before,types,after)
local isretval = not types:find(':') -- if we're replacing the retval, there is no ':' in types
-- convert floats to doubles since Lua doesn't use floats. Avoids unexpected junk in the insignificant figures
types = types:gsub('float', 'double')
-- any non-pointer types that can't be used with ydb_call_variadic_plist_func(), convert to pointer types
for _,typ in pairs{'ydb_double_t','ydb_int_t','double','int'} do
types = types:gsub('(' .. typ .. '%s*)[*]*([,) ])', '%1*%2')
end
-- convert any non-pointer retvals (which are illegal) to pointer types
if isretval and not types:find('void') then types=types:gsub('([A-Za-z0-9_]+)[%s*]+', '%1* ') end
if ydb_release < 1.36 then
-- make ydb <1.36 emulate 'buffer' type, even though it's not as good as string for output-only (O:) parameters
types = types:gsub('ydb_buffer_t%s*%*', 'ydb_string_t*')
end
if ydb_release >= 1.36 then
-- convert between buffer/string types for best efficiency (lets user use either one without thinking about it)
types = types:gsub('IO%s*:%s*ydb_string_t%s*%*', 'IO:ydb_buffer_t*') -- IO: params
types = types:gsub('IO%s*:%s*string%s*%*', 'IO:ydb_buffer_t*')
types = types:gsub('([^IO][IO])%s*:%s*ydb_buffer_t%s*%*', '%1:ydb_string_t*') -- I: or O: params
-- always replace 'buffer' with 'string' in the retval as it is equivalent of an O: param
if isretval then types=types:gsub('ydb_buffer_t%s*%*', 'ydb_string_t*') end
end
return before..types..after
end
-- replace things in parameters, i.e. between parentheses, and then replace things in retvals, i.e. immediately after first ':'
-- note: `(\1?)` is my substitute for an empty capture since `()` is a special Lua code that returns the position in the string
Mprototypes = Mprototypes:gsub('(\1?)(%([^\n]*%))(\1?)', type_replacer )
Mprototypes = Mprototypes:gsub('^(%s*[A-Za-z0-9_]+%s*:%s*)([^%s*]+[%s*]+)(\1?)', type_replacer) -- first line's retval
Mprototypes = Mprototypes:gsub('(\n%s*[A-Za-z0-9_]+%s*:%s*)([^%s*]+[%s*]+)(\1?)', type_replacer) -- other lines' retval
-- remove preallocation specs for ydb which can only process them in call-out tables not call-in tables
local ydb_prototypes = Mprototypes -- take a copy for our own parser which requires preallocation specs
Mprototypes = Mprototypes:gsub('%b[]', '')
-- write call-in file and load into ydb
local filename = os.tmpname()
local f = assert(io.open(filename, 'w'), string.format("cannot open temporary call-in file %q", filename))
assert(f:write(Mprototypes), string.format("cannot write to temporary call-in file %q", filename))
f:close()
local ci_handle = _yottadb.ci_tab_open(filename)
if debug then routines.__ci_filename = filename end
routines.__ci_table_handle = ci_handle
-- process ci-table ourselves to get routine names and types to cast
-- do this after ydb has handled it to let ydb catch any errors in the table
local line_no = 0
for line in ydb_prototypes:gmatch('([^\n]*)\n?') do
line_no = line_no+1
local ok, routine, func = pcall(parse_prototype, line, ci_handle)
if routine then routines[routine] = func end
end
if not debug then os.remove(filename) end -- cleanup
return routines
end
--- @section end
--- Node class operations
-- @section
--- Creates an object that represents a YottaDB node.
-- This node has all of the class methods defined below.
-- Calling the returned node with one or more string parameters returns a new node further subscripted by those strings.
-- Calling this on an existing node `yottadb.node(node)` creates an (immutable) copy of node.
--
-- *Notes:*
--
-- * Several standard Lua operators work on nodes. These are: `+` `-` `=` `pairs()` `tostring()`
-- * Although the syntax `node:method()` is pretty, be aware that it is slow. If you need speed, prefix the node method
-- with two underscores, `node:__method()`, which is equivalent, but 15x faster.
-- The former is slow because in Lua, `node:method(...)` is syntactic sugar which expands to `node.method(node, ...)`,
-- causing lua-yottadb to create an intermediate node object `node.method`. It is only when this new object gets called
-- with `(node, ...)`, and the first parameter is of type `node`, that the `__call` metamethod detects it was supposed to
-- be a method access and invokes `node.__method()`, discarding the intermediate subnode object it created.
-- * Because the `__` prefix accesses *method* names (as above), it cannot access database subnode names starting with `__`.
-- Instead, use mynode('__nodename') to access a database node named `__nodename`.
-- * This `__` prefix handling also means that object method names that start with two underscores, like `__tostring`,
-- are only accessible with an *additional* `__` prefix; for example, `node:____tostring()`.
-- @param varname String variable name.
-- @param[opt] subsarray table of subscripts
-- @param[opt] ... list of subscripts to append after any elements in optional subsarray table
-- @param node `|key:` is an existing node or key to copy into a new object (you can turn a `key` type into a `node` type this way)
-- @usage yottadb.node('varname'[, {subsarray}][, ...])
-- yottadb.node(node|key[, {}][, ...])
-- yottadb.node('varname')('sub1', 'sub2')
-- yottadb.node('varname', 'sub1', 'sub2')
-- yottadb.node('varname', {'sub1', 'sub2'})
-- yottadb.node('varname').sub1.sub2
-- yottadb.node('varname')['sub1']['sub2']
-- @return node object with metatable `yottadb.node`
function M.node(varname, ...)
local self
if type(varname) == 'string' then
self = _yottadb.cachearray_create(varname, ...)
_yottadb.cachearray_setmetatable(self, node) -- change to node type
else
assert(type(varname)=='userdata', "Parameter 1 must be varname (string) or a key/node object to create new node object from")
self = _yottadb.cachearray_create(varname) -- also makes it non-mutable
_yottadb.cachearray_setmetatable(self, node) -- change to node type
local subs = select('#', ...)
if subs>0 and type(...)=='table' then
self = _yottadb.cachearray_append(self, table.unpack(...))
if subs > 1 then
subsarray = {...}
table.remove(subsarray, 1)
self = _yottadb.cachearray_append(self, table.unpack(subsarray))
end
else
self = _yottadb.cachearray_append(self, ...)
end
end
return self
end
--- Tests whether object is a node object or inherits from a node object.
-- @param object to test
-- @return boolean true or false
function M.isnode(object)
if type(object) ~= 'userdata' then return false end
local class = getmetatable(object)
while class do
if class == node then return true end
class = rawget(class, '__superclass')
end
return false
end
--- @section end
--- Node class
-- @section
--- Get `node`'s value.
-- Equivalent to `node.__`, but 2.5x slower.
-- If node is subclassed, then `node.__` invokes the subclass's `node:__get()` if it exists.
-- @param[opt] default specify the value to return if the node has no data; if not supplied, `nil` is the default
-- @return value of the node
-- @see get
function node:get(default) return _yottadb.get(self) or default end
--- Set `node`'s value.
-- Equivalent to `node.__ = x`, but 4x slower.
-- If node is subclassed, then `node.__ = x` invokes the subclass's `node:__set(x)` if it exists.
-- @param value New value or `nil` to delete node
-- @return value
-- @see set