Name | Executed | Routines | % | Executed | Lines | % | Unexecuted |
/home/matt/eu/rds/include/std/eds.e | 44 | 45 | 97.78% | 965 | 1084 | 89.02% | 119 |
# | Executed | |
1 | -- (c) Copyright - See License.txt | |
2 | -- | |
3 | --**** | |
4 | -- == Euphoria Database (EDS) | |
5 | -- | |
6 | -- < | |
7 | namespace eds | |
8 | ||
9 | include std/error.e | |
10 | include std/convert.e | |
11 | include std/io.e | |
12 | include std/filesys.e | |
13 | include std/get.e | |
14 | include std/pretty.e | |
15 | include std/machine.e | |
16 | include std/sequence.e | |
17 | include std/datetime.e | |
18 | include std/text.e | |
19 | include std/math.e | |
20 | ||
21 | --**** | |
22 | -- === Database File Format | |
23 | -- | |
24 | -- ==== Header | |
25 | -- * byte 0: magic number for this file-type: 77 | |
26 | -- * byte 1: version number (major) | |
27 | -- * byte 2: version number (minor) | |
28 | -- * byte 3: 4-byte pointer to block of table headers | |
29 | -- * byte 7: number of free blocks | |
30 | -- * byte 11: 4-byte pointer to block of free blocks | |
31 | -- | |
32 | -- ==== Block of table headers | |
33 | -- * -4: allocated size of this block (for possible reallocation) | |
34 | -- * 0: number of table headers currently in use | |
35 | -- * 4: table header1 | |
36 | -- * 16: table header2 | |
37 | -- * 28: etc. | |
38 | -- | |
39 | -- ==== Table header | |
40 | -- * 0: pointer to the name of this table | |
41 | -- * 4: total number of records in this table | |
42 | -- * 8: number of blocks of records | |
43 | -- * 12: pointer to the index block for this table | |
44 | -- | |
45 | -- There are two levels of pointers. The logical array of key pointers | |
46 | -- is split up across many physical blocks. A single index block | |
47 | -- is used to select the correct small block. This allows | |
48 | -- inserts and deletes to be made without having to shift a | |
49 | -- large number of key pointers. Only one small block needs to | |
50 | -- be adjusted. This is particularly helpful when the table contains | |
51 | -- many thousands of records. | |
52 | -- | |
53 | -- ==== Index block | |
54 | -- one per table | |
55 | -- | |
56 | -- * -4: allocated size of index block | |
57 | -- * 0: number of records in 1st block of key pointers | |
58 | -- * 4: pointer to 1st block | |
59 | -- * 8: number of records in 2nd " " | |
60 | -- * 12: pointer to 2nd block | |
61 | -- * 16: etc. | |
62 | -- | |
63 | -- ==== Block of key pointers | |
64 | -- many per table | |
65 | -- | |
66 | -- * -4: allocated size of this block in bytes | |
67 | -- * 0: key pointer 1 | |
68 | -- * 4: key pointer 2 | |
69 | -- * 8: etc. | |
70 | -- | |
71 | -- ==== Free list | |
72 | -- in ascending order of address | |
73 | -- | |
74 | -- * -4: allocated size of block of free blocks | |
75 | -- * 0: address of 1st free block | |
76 | -- * 4: size of 1st free block | |
77 | -- * 8: address of 2nd free block | |
78 | -- * 12: size of 2nd free block | |
79 | -- * 16: etc. | |
80 | -- | |
81 | -- The key value and the data value for a record are allocated space | |
82 | -- as needed. A pointer to the data value is stored just before the | |
83 | -- key value. Euphoria objects, key and data, are stored in a compact form. | |
84 | -- | |
85 | -- All allocated blocks have the size of the block in bytes, stored in the | |
86 | -- four bytes just before the address. | |
87 | ||
88 | --**** | |
89 | -- === Error Status Constants | |
90 | ||
91 | public constant | |
92 | --** Database is OK, not error has occurred. | |
93 | 101 | DB_OK = 0, |
94 | --** The database could not be opened. | |
95 | 101 | DB_OPEN_FAIL = -1, |
96 | --** The database could not be created, it already exists. | |
97 | 101 | DB_EXISTS_ALREADY = -2, |
98 | --** A lock could not be gained on the database. | |
99 | 101 | DB_LOCK_FAIL = -3, |
100 | --** A fatal error has occurred. | |
101 | 101 | DB_FATAL_FAIL = -404 |
102 | ||
103 | --**** | |
104 | -- === Lock Type Constants | |
105 | ||
106 | public enum | |
107 | --** Do not lock the file. | |
108 | 101 | DB_LOCK_NO = 0, |
109 | --** Open the database with read-only access. | |
110 | 101 | DB_LOCK_SHARED, |
111 | --** Open the database with read and write access. | |
112 | 101 | DB_LOCK_EXCLUSIVE |
113 | ||
114 | --**** | |
115 | -- === Error Code Constants | |
116 | ||
117 | public enum | |
118 | --** Missing 0 terminator | |
119 | 101 | MISSING_END = 900, |
120 | --** current_db is not set | |
121 | 101 | NO_DATABASE, |
122 | --** seek() failed. | |
123 | 101 | BAD_SEEK, |
124 | --** no table was found. | |
125 | 101 | NO_TABLE, |
126 | --** this table already exists. | |
127 | 101 | DUP_TABLE, |
128 | --** unknown key_location index was supplied. | |
129 | 101 | BAD_RECNO, |
130 | --** couldn't insert a new record. | |
131 | 101 | INSERT_FAILED, |
132 | --** last error code | |
133 | 101 | LAST_ERROR_CODE, |
134 | --** bad file | |
135 | 101 | BAD_FILE |
136 | ||
137 | 101 | constant DB_MAGIC = 77 |
138 | 101 | constant DB_MAJOR = 4, DB_MINOR = 0 -- database created with Euphoria v4.0 |
139 | 101 | constant SIZEOF_TABLE_HEADER = 16 |
140 | 101 | constant TABLE_HEADERS = 3, FREE_COUNT = 7, FREE_LIST = 11 --, SIZEOF_DATABASE_HEADER = 14 |
141 | ||
142 | ||
143 | -- initial sizes for various things: | |
144 | 101 | constant INIT_FREE = 5, |
145 | 101 | INIT_TABLES = 5, |
146 | 101 | INIT_INDEX = 10, |
147 | 101 | INIT_RECORDS = 50 |
148 | ||
149 | 101 | constant TRUE = 1 |
150 | ||
151 | 101 | integer current_db = -1 |
152 | 101 | atom current_table_pos = -1 |
153 | 101 | sequence current_table_name = "" |
154 | 101 | sequence db_names = {} |
155 | 101 | sequence db_file_nums = {} |
156 | 101 | sequence db_lock_methods = {} |
157 | 101 | integer current_lock = 0 |
158 | 101 | sequence key_pointers = {} |
159 | 101 | sequence key_cache = {} |
160 | 101 | sequence cache_index = {} |
161 | 101 | integer caching_option = 1 |
162 | ||
163 | atom void | |
164 | ||
165 | ||
166 | --**** | |
167 | -- === Variables | |
168 | -- | |
169 | ||
170 | --** | |
171 | -- **Exception handler**\\ | |
172 | -- Set this to a valid routine_id value for a procedure that | |
173 | -- will be called whenever the library detects a serious error. You procedure | |
174 | -- will be passed a single text string that describes the error. It may also | |
175 | -- call [[:db_get_errors]] to get more detail about the cause of the error. | |
176 | ||
177 | public integer db_fatal_id | |
178 | ||
179 | 101 | db_fatal_id = DB_FATAL_FAIL -- Initialized separately from declaration so |
180 | -- the initial value doesn't show up in docs. | |
181 | ||
182 | ||
183 | 101 | sequence vLastErrors = {} |
184 | ||
185 | ||
186 | 6 | |
187 | 6 | vLastErrors = append(vLastErrors, {errcode, msg, routine_name, parms}) |
188 | 6 | if db_fatal_id >= 0 then |
189 | 5 | call_proc(db_fatal_id, {sprintf("Error Code %d: %s, from %s", {errcode, msg, routine_name})}) |
190 | end if | |
191 | 6 | end procedure |
192 | ||
193 | 6 | |
194 | -- read 1-byte value at current position in database file | |
195 | 6 | return getc(current_db) |
196 | end function | |
197 | ||
198 | atom mem0, mem1, mem2, mem3 | |
199 | 101 | mem0 = allocate(4) |
200 | 101 | mem1 = mem0 + 1 |
201 | 101 | mem2 = mem0 + 2 |
202 | 101 | mem3 = mem0 + 3 |
203 | ||
204 | 47338 | |
205 | -- read 4-byte value at current position in database file | |
206 | 47338 | poke(mem0, getc(current_db)) |
207 | 47338 | poke(mem1, getc(current_db)) |
208 | 47338 | poke(mem2, getc(current_db)) |
209 | 47338 | poke(mem3, getc(current_db)) |
210 | 47338 | return peek4u(mem0) |
211 | end function | |
212 | ||
213 | 17 | |
214 | -- read a 0-terminated string at current position in database file | |
215 | sequence s | |
216 | integer c | |
217 | integer i | |
218 | ||
219 | 17 | s = repeat(0, 256) |
220 | 17 | i = 0 |
221 | 17 | while c with entry do |
222 | 109 | if c = -1 then |
223 | 0 | fatal(MISSING_END, "string is missing 0 terminator", "get_string", {where(current_db)}) |
224 | 0 | exit |
225 | end if | |
226 | 109 | i += 1 |
227 | 109 | if i > length(s) then |
228 | 0 | s &= repeat(0, 256) |
229 | end if | |
230 | 109 | s[i] = c |
231 | entry | |
232 | 126 | c = getc(current_db) |
233 | 126 | end while |
234 | 17 | return s[1..i] |
235 | end function | |
236 | ||
237 | 274 | |
238 | -- test if string at current position in database file equals given string | |
239 | integer c | |
240 | integer i | |
241 | ||
242 | 274 | i = 0 |
243 | 274 | while c with entry do |
244 | 2193 | if c = -1 then |
245 | 0 | fatal(MISSING_END, "string is missing 0 terminator", "equal_string", {where(current_db)}) |
246 | 0 | return DB_FATAL_FAIL |
247 | end if | |
248 | 2193 | i += 1 |
249 | 2193 | if i > length(target) then |
250 | 0 | return 0 |
251 | end if | |
252 | 2193 | if target[i] != c then |
253 | 199 | return 0 |
254 | end if | |
255 | entry | |
256 | 2268 | c = getc(current_db) |
257 | 2268 | end while |
258 | 75 | return (i = length(target)) |
259 | end function | |
260 | ||
261 | -- Compressed format of Euphoria objects on disk | |
262 | -- | |
263 | -- First byte: | |
264 | -- 0..248 -- immediate small integer, -9 to 239 | |
265 | -- since small negative integers -9..-1 might be common | |
266 | 101 | constant I2B = 249, -- 2-byte signed integer follows |
267 | 101 | I3B = 250, -- 3-byte signed integer follows |
268 | 101 | I4B = 251, -- 4-byte signed integer follows |
269 | 101 | F4B = 252, -- 4-byte f.p. number follows |
270 | 101 | F8B = 253, -- 8-byte f.p. number follows |
271 | 101 | S1B = 254, -- sequence, 1-byte length follows, then elements |
272 | 101 | S4B = 255 -- sequence, 4-byte length follows, then elements |
273 | ||
274 | 101 | constant MIN1B = -9, |
275 | 101 | MAX1B = 239, |
276 | 101 | MIN2B = -power(2, 15), |
277 | 101 | MAX2B = power(2, 15)-1, |
278 | 101 | MIN3B = -power(2, 23), |
279 | 101 | MAX3B = power(2, 23)-1, |
280 | 101 | MIN4B = -power(2, 31) |
281 | ||
282 | 5621 | |
283 | -- read a compressed Euphoria object from disk | |
284 | -- if c is set, then c is not <= 248 | |
285 | sequence s | |
286 | integer len | |
287 | ||
288 | 5621 | if c = 0 then |
289 | 5619 | c = getc(current_db) |
290 | 5619 | if c < I2B then |
291 | 3552 | return c + MIN1B |
292 | end if | |
293 | end if | |
294 | ||
295 | 2069 | switch c with fallthru do |
296 | case I2B then | |
297 | 1946 | return getc(current_db) + |
298 | #100 * getc(current_db) + | |
299 | MIN2B | |
300 | ||
301 | case I3B then | |
302 | 0 | return getc(current_db) + |
303 | #100 * getc(current_db) + | |
304 | #10000 * getc(current_db) + | |
305 | MIN3B | |
306 | ||
307 | case I4B then | |
308 | 0 | return get4() + MIN4B |
309 | ||
310 | case F4B then | |
311 | 0 | return float32_to_atom({getc(current_db), getc(current_db), |
312 | getc(current_db), getc(current_db)}) | |
313 | ||
314 | case F8B then | |
315 | 0 | return float64_to_atom({getc(current_db), getc(current_db), |
316 | getc(current_db), getc(current_db), | |
317 | getc(current_db), getc(current_db), | |
318 | getc(current_db), getc(current_db)}) | |
319 | ||
320 | case else | |
321 | -- sequence | |
322 | 123 | if c = S1B then |
323 | 123 | len = getc(current_db) |
324 | else | |
325 | 0 | len = get4() |
326 | end if | |
327 | 123 | s = repeat(0, len) |
328 | 123 | for i = 1 to len do |
329 | -- in-line small integer for greater speed on strings | |
330 | 1198 | c = getc(current_db) |
331 | 1198 | if c < I2B then |
332 | 1196 | s[i] = c + MIN1B |
333 | else | |
334 | 2 | s[i] = decompress(c) |
335 | end if | |
336 | 1198 | end for |
337 | 123 | return s |
338 | end switch | |
339 | end function | |
340 | ||
341 | 9876 | |
342 | -- return the compressed representation of a Euphoria object | |
343 | -- as a sequence of bytes | |
344 | sequence x4, s | |
345 | ||
346 | 9876 | if integer(x) then |
347 | 9142 | if x >= MIN1B and x <= MAX1B then |
348 | 8881 | return {x - MIN1B} |
349 | ||
350 | 261 | elsif x >= MIN2B and x <= MAX2B then |
351 | 261 | x -= MIN2B |
352 | 261 | return {I2B, and_bits(x, #FF), floor(x / #100)} |
353 | ||
354 | 0 | elsif x >= MIN3B and x <= MAX3B then |
355 | 0 | x -= MIN3B |
356 | 0 | return {I3B, and_bits(x, #FF), and_bits(floor(x / #100), #FF), floor(x / #10000)} |
357 | ||
358 | else | |
359 | 0 | return I4B & int_to_bytes(x-MIN4B) |
360 | ||
361 | end if | |
362 | ||
363 | 734 | elsif atom(x) then |
364 | -- floating point | |
365 | 0 | x4 = atom_to_float32(x) |
366 | 0 | if x = float32_to_atom(x4) then |
367 | -- can represent as 4-byte float | |
368 | 0 | return F4B & x4 |
369 | else | |
370 | 0 | return F8B & atom_to_float64(x) |
371 | end if | |
372 | ||
373 | else | |
374 | -- sequence | |
375 | 734 | if length(x) <= 255 then |
376 | 734 | s = {S1B, length(x)} |
377 | else | |
378 | 0 | s = S4B & int_to_bytes(length(x)) |
379 | end if | |
380 | 734 | for i = 1 to length(x) do |
381 | 8461 | s &= compress(x[i]) |
382 | 8461 | end for |
383 | 734 | return s |
384 | end if | |
385 | end function | |
386 | ||
387 | 15 | |
388 | -- write 1 byte to current database file | |
389 | 15 | puts(current_db, x) |
390 | 15 | end procedure |
391 | ||
392 | sequence memseq | |
393 | 101 | memseq = {mem0, 4} |
394 | ||
395 | 6672 | |
396 | -- write 4 bytes to current database file | |
397 | -- x is 32-bits max | |
398 | 6672 | poke4(mem0, x) -- faster than doing divides etc. |
399 | 6672 | puts(current_db, peek(memseq)) |
400 | 6672 | end procedure |
401 | ||
402 | 1657 | |
403 | -- write a sequence of bytes to current database file | |
404 | 1657 | puts(current_db, s) |
405 | 1657 | end procedure |
406 | ||
407 | 222 | |
408 | -- seek to a position in the current db file | |
409 | 222 | if current_db = -1 then |
410 | 0 | fatal(NO_DATABASE, "no current database defined", "safe_seek", {pos}) |
411 | 0 | return |
412 | end if | |
413 | 222 | if seek(current_db, pos) != 0 then |
414 | 0 | fatal(BAD_SEEK, "seek to position failed", "safe_seek", {pos}) |
415 | 0 | return |
416 | end if | |
417 | 222 | end procedure |
418 | ||
419 | --**** | |
420 | -- === Routines | |
421 | ||
422 | --** | |
423 | -- Fetches the most recent set of errors recorded by the library. | |
424 | -- | |
425 | -- Parameters: | |
426 | -- # ##clearing## : if zero the set of errors is not reset, otherwise | |
427 | -- it will be cleared out. The default is to clear the set. | |
428 | -- | |
429 | -- Returns: | |
430 | -- A **sequence**, each element is a set of four fields. | |
431 | -- # Error Code. | |
432 | -- # Error Text. | |
433 | -- # Name of library routine that recorded the error. | |
434 | -- # Parameters passed to that routine. | |
435 | -- | |
436 | -- Comments: | |
437 | -- * A number of library routines can detect errors. If the routine is a | |
438 | -- function, it usually returns an error code. However, procedures that | |
439 | -- detect an error can't do that. Instead, they record the error details | |
440 | -- and you can query that after calling the library routine. | |
441 | -- * Both functions and procedures that detect errors record the details | |
442 | -- in the ##Last Error Set##, which is fetched by this function. | |
443 | -- | |
444 | -- | |
445 | -- Example 1: | |
446 | -- | |
447 | -- db_replace_data(recno, new_data) | |
448 | -- errs = db_get_errors() | |
449 | -- if length(errs) != 0 then | |
450 | -- display_errors(errs) | |
451 | -- abort(1) | |
452 | -- end if | |
453 | -- | |
454 | ||
455 | 7 | |
456 | sequence lErrors | |
457 | ||
458 | 7 | lErrors = vLastErrors |
459 | 7 | if clearing then |
460 | 7 | vLastErrors = {} |
461 | end if | |
462 | ||
463 | 7 | return lErrors |
464 | end function | |
465 | ||
466 | --** | |
467 | -- print the current database in readable form to file fn | |
468 | -- | |
469 | -- Parameters: | |
470 | -- # ##fn## : the destination file for printing the current Euphoria database; | |
471 | -- # ##low_level_too## : a boolean. If true, a byte-by-byte binary dump | |
472 | -- is presented as well; otherwise this step is skipped. If omitted, | |
473 | -- //false// is assumed. | |
474 | -- | |
475 | -- Errors: | |
476 | -- If the current database is not defined, an error will occur. | |
477 | -- | |
478 | -- Comments: | |
479 | -- * All records in all tables are shown. | |
480 | -- * If low_level_too is non-zero, | |
481 | -- then a low-level byte-by-byte dump is also shown. The low-level | |
482 | -- dump will only be meaningful to someone who is familiar | |
483 | -- with the internal format of a Euphoria database. | |
484 | -- | |
485 | -- Example 1: | |
486 | -- | |
487 | -- if db_open("mydata", DB_LOCK_SHARED) != DB_OK then | |
488 | -- puts(2, "Couldn't open the database!\n") | |
489 | -- abort(1) | |
490 | -- end if | |
491 | -- fn = open("db.txt", "w") | |
492 | -- db_dump(fn) -- Simple output | |
493 | -- db_dump("lowlvl_db.txt", 1) -- Full low-level dump created. | |
494 | -- | |
495 | ||
496 | 2 | |
497 | -- print an open database in readable form to file fn | |
498 | -- (Note: If you turn database.e into a .dll or .so, you will | |
499 | -- have to use a file name, rather than an open file number. | |
500 | -- All other database.e routines are ok as they are.) | |
501 | integer magic, minor, major | |
502 | integer fn | |
503 | atom tables, ntables, tname, trecords, t_header, tnrecs, | |
504 | key_ptr, data_ptr, size, addr, tindex, fbp | |
505 | object key, data | |
506 | integer c, n, tblocks | |
507 | atom a | |
508 | sequence ll_line | |
509 | integer hi, ci | |
510 | ||
511 | 2 | if sequence(file_id) then |
512 | 1 | fn = open(file_id, "w") |
513 | 1 | elsif file_id > 0 then |
514 | 0 | fn = file_id |
515 | 0 | puts(fn, '\n') |
516 | else | |
517 | 1 | fn = file_id |
518 | end if | |
519 | 2 | if fn <= 0 then |
520 | 1 | fatal( BAD_FILE, "bad file", "db_dump", {file_id, low_level_too}) |
521 | 1 | return |
522 | end if | |
523 | ||
524 | 1 | printf(fn, "Database dump as at %s\n", {datetime:format(now(), "%Y-%m-%d %H:%M:%S")}) |
525 | 1 | safe_seek(0) |
526 | 1 | if length(vLastErrors) > 0 then return end if |
527 | 1 | magic = get1() |
528 | 1 | if magic != DB_MAGIC then |
529 | 0 | puts(fn, "This is not a Euphoria Database file.\n") |
530 | else | |
531 | 1 | major = get1() |
532 | 1 | minor = get1() |
533 | 1 | printf(fn, "Euphoria Database System Version %d.%d\n\n", {major, minor}) |
534 | 1 | tables = get4() |
535 | 1 | void = seek(current_db, tables) |
536 | 1 | ntables = get4() |
537 | 1 | printf(fn, "The \"%s\" database has %d table", |
538 | {db_names[eu:find(current_db, db_file_nums)], ntables}) | |
539 | 1 | if ntables = 1 then |
540 | 0 | puts(fn, "\n") |
541 | else | |
542 | 1 | puts(fn, "s\n") |
543 | end if | |
544 | end if | |
545 | ||
546 | 1 | if low_level_too then |
547 | -- low level dump: show all bytes in the file | |
548 | 1 | puts(fn, " Disk Dump\nDiskAddr " & repeat('-', 58)) |
549 | 1 | void = seek(current_db, 0) |
550 | 1 | a = 0 |
551 | 1 | while c >= 0 with entry do |
552 | ||
553 | 789 | if c = -1 then |
554 | 0 | exit |
555 | end if | |
556 | 789 | if remainder(a, 16) = 0 then |
557 | 50 | if a > 0 then |
558 | 49 | printf(fn, "%s\n", {ll_line}) |
559 | else | |
560 | 1 | puts(fn, '\n') |
561 | end if | |
562 | 50 | ll_line = repeat(' ', 67) |
563 | 50 | ll_line[9] = ':' |
564 | 50 | ll_line[48] = '|' |
565 | 50 | ll_line[67] = '|' |
566 | 50 | hi = 11 |
567 | 50 | ci = 50 |
568 | 50 | ll_line[1..8] = sprintf("%08x", a) |
569 | end if | |
570 | 789 | ll_line[hi .. hi + 1] = sprintf("%02x", c) |
571 | 789 | hi += 2 |
572 | 789 | if eu:find(hi, {19, 28, 38}) then |
573 | 148 | hi += 1 |
574 | 148 | if hi = 29 then |
575 | 49 | hi = 30 |
576 | end if | |
577 | end if | |
578 | 789 | if c > ' ' and c < '~' then |
579 | 21 | ll_line[ci] = c |
580 | else | |
581 | 768 | ll_line[ci] = '.' |
582 | end if | |
583 | 789 | ci += 1 |
584 | ||
585 | 789 | a += 1 |
586 | entry | |
587 | 790 | c = getc(current_db) |
588 | 790 | end while |
589 | 1 | printf(fn, "%s\n", {ll_line}) |
590 | 1 | puts(fn, repeat('-', 67) & "\n\n") |
591 | end if | |
592 | ||
593 | -- high level dump | |
594 | 1 | void = seek(current_db, 0) |
595 | 1 | magic = get1() |
596 | 1 | if magic != DB_MAGIC then |
597 | 0 | if sequence(file_id) then |
598 | 0 | close(fn) |
599 | end if | |
600 | 0 | return |
601 | end if | |
602 | ||
603 | 1 | major = get1() |
604 | 1 | minor = get1() |
605 | ||
606 | 1 | tables = get4() |
607 | 1 | if low_level_too then printf(fn, "[tables:#%08x]\n", tables) end if |
608 | 1 | void = seek(current_db, tables) |
609 | 1 | ntables = get4() |
610 | 1 | t_header = where(current_db) |
611 | 1 | for t = 1 to ntables do |
612 | 2 | if low_level_too then printf(fn, "\n---------------\n[table header:#%08x]\n", t_header) end if |
613 | -- display the next table | |
614 | 2 | tname = get4() |
615 | 2 | tnrecs = get4() |
616 | 2 | tblocks = get4() |
617 | 2 | tindex = get4() |
618 | 2 | if low_level_too then printf(fn, "[table name:#%08x]\n", tname) end if |
619 | 2 | void = seek(current_db, tname) |
620 | 2 | printf(fn, "\ntable \"%s\", records:%d indexblks: %d\n\n\n", {get_string(), tnrecs, tblocks}) |
621 | 2 | if tnrecs > 0 then |
622 | 1 | for b = 1 to tblocks do |
623 | 1 | if low_level_too then printf(fn, "[table block %d:#%08x]\n", {b, tindex+(b-1)*8}) end if |
624 | 1 | void = seek(current_db, tindex+(b-1)*8) |
625 | 1 | tnrecs = get4() |
626 | 1 | trecords = get4() |
627 | 1 | if tnrecs > 0 then |
628 | 1 | printf(fn, "\n--------------------------\nblock #%d, ptrs:%d\n--------------------------\n", {b, tnrecs}) |
629 | 1 | for r = 1 to tnrecs do |
630 | -- display the next record | |
631 | 2 | if low_level_too then printf(fn, "[record %d:#%08x]\n", {r, trecords+(r-1)*4}) end if |
632 | 2 | void = seek(current_db, trecords+(r-1)*4) |
633 | 2 | key_ptr = get4() |
634 | 2 | if low_level_too then printf(fn, "[key %d:#%08x]\n", {r, key_ptr}) end if |
635 | 2 | void = seek(current_db, key_ptr) |
636 | 2 | data_ptr = get4() |
637 | 2 | key = decompress(0) |
638 | 2 | puts(fn, " key: ") |
639 | 2 | pretty_print(fn, key, {2, 2, 8}) |
640 | 2 | puts(fn, '\n') |
641 | 2 | if low_level_too then printf(fn, "[data %d:#%08x]\n", {r, data_ptr}) end if |
642 | 2 | void = seek(current_db, data_ptr) |
643 | 2 | data = decompress(0) |
644 | 2 | puts(fn, " data: ") |
645 | 2 | pretty_print(fn, data, {2, 2, 9}) |
646 | 2 | puts(fn, "\n\n") |
647 | 2 | end for |
648 | else | |
649 | 0 | printf(fn, "\nblock #%d (empty)\n\n", b) |
650 | end if | |
651 | 1 | end for |
652 | end if | |
653 | 2 | t_header += SIZEOF_TABLE_HEADER |
654 | 2 | void = seek(current_db, t_header) |
655 | 2 | end for |
656 | -- show the free list | |
657 | 1 | if low_level_too then printf(fn, "[free blocks:#%08x]\n", FREE_COUNT) end if |
658 | 1 | void = seek(current_db, FREE_COUNT) |
659 | 1 | n = get4() |
660 | 1 | puts(fn, '\n') |
661 | 1 | if n > 0 then |
662 | 1 | fbp = get4() |
663 | 1 | printf(fn, "Number of Free blocks: %d ", n) |
664 | 1 | if low_level_too then printf(fn, " [#%08x]:", fbp) end if |
665 | 1 | puts(fn, '\n') |
666 | 1 | void = seek(current_db, fbp) |
667 | 1 | for i = 1 to n do |
668 | 2 | addr = get4() |
669 | 2 | size = get4() |
670 | 2 | printf(fn, "%08x: %6d bytes\n", {addr, size}) |
671 | 2 | end for |
672 | else | |
673 | 0 | puts(fn, "No free blocks available.\n") |
674 | end if | |
675 | 1 | if sequence(file_id) then |
676 | 1 | close(fn) |
677 | end if | |
678 | ||
679 | 1 | end procedure |
680 | ||
681 | --** | |
682 | -- Detects corruption of the free list in a Euphoria database. | |
683 | -- | |
684 | -- Comments: | |
685 | -- This is a debug routine used by RDS to detect corruption of the free list. | |
686 | -- Users do not normally call this. | |
687 | -- | |
688 | 4 | |
689 | atom free_count, free_list, addr, size, free_list_space | |
690 | atom max | |
691 | ||
692 | 4 | safe_seek(-1) |
693 | 4 | if length(vLastErrors) > 0 then return end if |
694 | 4 | max = where(current_db) |
695 | 4 | void = seek(current_db, FREE_COUNT) |
696 | 4 | free_count = get4() |
697 | 4 | if free_count > max/13 then |
698 | 0 | crash("free count is too high") |
699 | end if | |
700 | 4 | free_list = get4() |
701 | 4 | if free_list > max then |
702 | 0 | crash("bad free list pointer") |
703 | end if | |
704 | 4 | void = seek(current_db, free_list - 4) |
705 | 4 | free_list_space = get4() |
706 | 4 | if free_list_space > max or free_list_space < 0 then |
707 | 0 | crash("free list space is bad") |
708 | end if | |
709 | 4 | for i = 0 to free_count - 1 do |
710 | 51 | void = seek(current_db, free_list + i * 8) |
711 | 51 | addr = get4() |
712 | 51 | if addr > max then |
713 | 0 | crash("bad block address") |
714 | end if | |
715 | 51 | size = get4() |
716 | 51 | if size > max then |
717 | 0 | crash("block size too big") |
718 | end if | |
719 | 51 | void = seek(current_db, addr - 4) |
720 | 51 | if get4() > size then |
721 | 0 | crash("bad size in front of free block") |
722 | end if | |
723 | 51 | end for |
724 | 4 | end procedure |
725 | ||
726 | 1508 | |
727 | -- Allocate (at least) n bytes of space in the database file. | |
728 | -- The usable size + 4 is stored in the 4 bytes before the returned address. | |
729 | -- Upon return, the file pointer points at the allocated space, so data | |
730 | -- can be stored into the space immediately without a seek. | |
731 | -- When space is allocated at the end of the file, it will be exactly | |
732 | -- n bytes in size, and the caller must fill up all the space immediately. | |
733 | atom free_list, size, size_ptr, addr | |
734 | integer free_count | |
735 | sequence remaining | |
736 | ||
737 | 1508 | void = seek(current_db, FREE_COUNT) |
738 | 1508 | free_count = get4() |
739 | 1508 | if free_count > 0 then |
740 | 280 | free_list = get4() |
741 | 280 | void = seek(current_db, free_list) |
742 | 280 | size_ptr = free_list + 4 |
743 | 280 | for i = 1 to free_count do |
744 | 312 | addr = get4() |
745 | 312 | size = get4() |
746 | 312 | if size >= n+4 then |
747 | -- found a big enough block | |
748 | 275 | if size >= n+16 then |
749 | -- loose fit: shrink first part, return 2nd part | |
750 | 250 | void = seek(current_db, addr - 4) |
751 | 250 | put4(size-n-4) -- shrink the block |
752 | 250 | void = seek(current_db, size_ptr) |
753 | 250 | put4(size-n-4) -- update size on free list too |
754 | 250 | addr += size-n-4 |
755 | 250 | void = seek(current_db, addr - 4) |
756 | 250 | put4(n+4) |
757 | else | |
758 | -- close fit: remove whole block from list and return it | |
759 | 25 | remaining = get_bytes(current_db, (free_count-i) * 8) |
760 | 25 | void = seek(current_db, free_list+8*(i-1)) |
761 | 25 | putn(remaining) |
762 | 25 | void = seek(current_db, FREE_COUNT) |
763 | 25 | put4(free_count-1) |
764 | 25 | void = seek(current_db, addr - 4) |
765 | 25 | put4(size) -- in case size was not updated by db_free() |
766 | end if | |
767 | 275 | return addr |
768 | end if | |
769 | 37 | size_ptr += 8 |
770 | 37 | end for |
771 | end if | |
772 | -- no free block available - point to end of file | |
773 | 1233 | void = seek(current_db, -1) |
774 | 1233 | put4(n+4) |
775 | 1233 | return where(current_db) |
776 | end function | |
777 | ||
778 | 1094 | |
779 | -- Put a block of storage onto the free list in order of address. | |
780 | -- Combine the new free block with any adjacent free blocks. | |
781 | atom psize, i, size, addr, free_list, free_list_space | |
782 | atom new_space, to_be_freed, prev_addr, prev_size | |
783 | integer free_count | |
784 | sequence remaining | |
785 | ||
786 | 1094 | void = seek(current_db, p-4) |
787 | 1094 | psize = get4() |
788 | ||
789 | 1094 | void = seek(current_db, FREE_COUNT) |
790 | 1094 | free_count = get4() |
791 | 1094 | free_list = get4() |
792 | 1094 | void = seek(current_db, free_list - 4) |
793 | 1094 | free_list_space = get4()-4 |
794 | 1094 | if free_list_space < 8 * (free_count+1) then |
795 | -- need more space for free list | |
796 | 4 | new_space = floor(free_list_space + free_list_space / 2) |
797 | 4 | to_be_freed = free_list |
798 | 4 | free_list = db_allocate(new_space) |
799 | 4 | void = seek(current_db, FREE_COUNT) |
800 | 4 | free_count = get4() -- db_allocate may have changed it |
801 | 4 | void = seek(current_db, FREE_LIST) |
802 | 4 | put4(free_list) |
803 | 4 | void = seek(current_db, to_be_freed) |
804 | 4 | remaining = get_bytes(current_db, 8*free_count) |
805 | 4 | void = seek(current_db, free_list) |
806 | 4 | putn(remaining) |
807 | 4 | putn(repeat(0, new_space-length(remaining))) |
808 | 4 | void = seek(current_db, free_list) |
809 | else | |
810 | 1090 | new_space = 0 |
811 | end if | |
812 | ||
813 | 1094 | i = 1 |
814 | 1094 | prev_addr = 0 |
815 | 1094 | prev_size = 0 |
816 | 1094 | while i <= free_count do |
817 | 13913 | addr = get4() |
818 | 13913 | size = get4() |
819 | 13913 | if p < addr then |
820 | 119 | exit |
821 | end if | |
822 | 13794 | prev_addr = addr |
823 | 13794 | prev_size = size |
824 | 13794 | i += 1 |
825 | 13794 | end while |
826 | ||
827 | 1094 | if i > 1 and prev_addr + prev_size = p then |
828 | -- combine with previous block | |
829 | 998 | void = seek(current_db, free_list+(i-2)*8+4) |
830 | 998 | if i < free_count and p + psize = addr then |
831 | -- combine space for all 3, delete the following block | |
832 | 9 | put4(prev_size+psize+size) -- update size on free list (only) |
833 | 9 | void = seek(current_db, free_list+i*8) |
834 | 9 | remaining = get_bytes(current_db, (free_count-i)*8) |
835 | 9 | void = seek(current_db, free_list+(i-1)*8) |
836 | 9 | putn(remaining) |
837 | 9 | free_count -= 1 |
838 | 9 | void = seek(current_db, FREE_COUNT) |
839 | 9 | put4(free_count) |
840 | else | |
841 | 989 | put4(prev_size+psize) -- increase previous size on free list (only) |
842 | end if | |
843 | 96 | elsif i < free_count and p + psize = addr then |
844 | -- combine with following block - only size on free list is updated | |
845 | 35 | void = seek(current_db, free_list+(i-1)*8) |
846 | 35 | put4(p) |
847 | 35 | put4(psize+size) |
848 | else | |
849 | -- insert a new block, shift the others down | |
850 | 61 | void = seek(current_db, free_list+(i-1)*8) |
851 | 61 | remaining = get_bytes(current_db, (free_count-i+1)*8) |
852 | 61 | free_count += 1 |
853 | 61 | void = seek(current_db, FREE_COUNT) |
854 | 61 | put4(free_count) |
855 | 61 | void = seek(current_db, free_list+(i-1)*8) |
856 | 61 | put4(p) |
857 | 61 | put4(psize) |
858 | 61 | putn(remaining) |
859 | end if | |
860 | ||
861 | 1094 | if new_space then |
862 | 4 | db_free(to_be_freed) -- free the old space |
863 | end if | |
864 | 1094 | end procedure |
865 | ||
866 | 91 | |
867 | integer k | |
868 | 91 | if caching_option = 1 then |
869 | 85 | if current_table_pos > 0 then |
870 | 58 | k = eu:find({current_db, current_table_pos}, cache_index) |
871 | 58 | if k != 0 then |
872 | 34 | key_cache[k] = key_pointers |
873 | else | |
874 | 24 | key_cache = append(key_cache, key_pointers) |
875 | 24 | cache_index = append(cache_index, {current_db, current_table_pos}) |
876 | end if | |
877 | end if | |
878 | end if | |
879 | 91 | end procedure |
880 | ||
881 | --**** | |
882 | -- === Managing databases | |
883 | ||
884 | --** | |
885 | -- Create a new database, given a file path and a lock method. | |
886 | -- | |
887 | -- Parameters: | |
888 | -- # ##path## : a sequence, the path to the file that will contain the database. | |
889 | -- # ##lock_method## : an integer specifying which type of access can be | |
890 | -- granted to the database. The value of ##lock_method## | |
891 | -- can be either ##DB_LOCK_NO## (no lock) or | |
892 | -- ##DB_LOCK_EXCLUSIVE## (exclusive lock). | |
893 | -- # ##init_tables## : an integer giving the initial number of tables to | |
894 | -- reserve space for. The default is 5 and the minimum is 1. | |
895 | -- # ##init_free## : an integer giving the initial amount of free space pointers to | |
896 | -- reserve space for. The default is 5 and the minimum is 0. | |
897 | -- | |
898 | -- Returns: | |
899 | -- An **integer**, status code, either DB_OK if creation successful or anything else on an error. | |
900 | -- | |
901 | -- Comments: | |
902 | -- | |
903 | -- On success, the newly created database | |
904 | -- becomes the **current database** to which | |
905 | -- all other database operations will apply. | |
906 | -- | |
907 | -- If the path, s, does not end in .edb, it will be added automatically. | |
908 | -- | |
909 | -- A version number is stored in the database file so future | |
910 | -- versions of the database software can recognize the format, and | |
911 | -- possibly read it and deal with it in some way. | |
912 | -- | |
913 | -- If the database already exists, it will not be overwritten. | |
914 | -- db_create() will return DB_EXISTS_ALREADY. | |
915 | -- | |
916 | -- Example 1: | |
917 | -- | |
918 | -- if db_create("mydata", DB_LOCK_NO) != DB_OK then | |
919 | -- puts(2, "Couldn't create the database!\n") | |
920 | -- abort(1) | |
921 | -- end if | |
922 | -- | |
923 | -- | |
924 | -- See Also: | |
925 | -- [[:db_open]], [[:db_select]] | |
926 | ||
927 | 6 | |
928 | integer db | |
929 | ||
930 | 6 | if init_tables < 1 then |
931 | 0 | init_tables = 1 |
932 | end if | |
933 | ||
934 | 6 | if init_free < 0 then |
935 | 0 | init_free = 0 |
936 | end if | |
937 | ||
938 | 6 | if not eu:find('.', path) then |
939 | 0 | path &= ".edb" |
940 | end if | |
941 | ||
942 | -- see if it already exists | |
943 | 6 | db = open(path, "rb") |
944 | 6 | if db != -1 then |
945 | -- don't destroy an existing db - let user delete himself | |
946 | 1 | close(db) |
947 | 1 | return DB_EXISTS_ALREADY |
948 | end if | |
949 | ||
950 | -- file must exist before "ub" can be used | |
951 | 5 | db = open(path, "wb") |
952 | 5 | if db = -1 then |
953 | 0 | return DB_OPEN_FAIL |
954 | end if | |
955 | 5 | close(db) |
956 | ||
957 | -- get read and write access, "ub" | |
958 | 5 | db = open(path, "ub") |
959 | 5 | if db = -1 then |
960 | 0 | return DB_OPEN_FAIL |
961 | end if | |
962 | 5 | if lock_method = DB_LOCK_SHARED then |
963 | -- shared lock doesn't make sense for create | |
964 | 0 | lock_method = DB_LOCK_NO |
965 | end if | |
966 | 5 | if lock_method = DB_LOCK_EXCLUSIVE then |
967 | 1 | if not lock_file(db, LOCK_EXCLUSIVE, {}) then |
968 | 0 | return DB_LOCK_FAIL |
969 | end if | |
970 | end if | |
971 | 5 | save_keys() |
972 | 5 | current_db = db |
973 | 5 | current_lock = lock_method |
974 | 5 | current_table_pos = -1 |
975 | 5 | current_table_name = "" |
976 | 5 | db_names = append(db_names, path) |
977 | 5 | db_lock_methods = append(db_lock_methods, lock_method) |
978 | 5 | db_file_nums = append(db_file_nums, db) |
979 | ||
980 | -- initialize the header | |
981 | 5 | put1(DB_MAGIC) -- so we know what type of file it is |
982 | 5 | put1(DB_MAJOR) -- major version |
983 | 5 | put1(DB_MINOR) -- minor version |
984 | -- 3: | |
985 | 5 | put4(19) -- pointer to tables |
986 | -- 7: | |
987 | 5 | put4(0) -- number of free blocks |
988 | -- 11: | |
989 | 5 | put4(23 + init_tables * SIZEOF_TABLE_HEADER + 4) -- pointer to free list |
990 | -- 15: initial table block: | |
991 | 5 | put4( 8 + init_tables * SIZEOF_TABLE_HEADER) -- allocated size |
992 | -- 19: | |
993 | 5 | put4(0) -- number of tables that currently exist |
994 | -- 23: initial space for tables | |
995 | 5 | putn(repeat(0, init_tables * SIZEOF_TABLE_HEADER)) |
996 | -- initial space for free list | |
997 | 5 | put4(4+init_free*8) -- allocated size |
998 | 5 | putn(repeat(0, init_free * 8)) |
999 | 5 | return DB_OK |
1000 | end function | |
1001 | ||
1002 | --** | |
1003 | -- Open an existing Euphoria database. | |
1004 | -- | |
1005 | -- Parameters: | |
1006 | -- # ##path## : a sequence, the path to the file containing the database | |
1007 | -- # ##lock_method## : an integer specifying which sort of access can | |
1008 | -- be granted to the database. The types of lock that you can use are: | |
1009 | -- ## ##DB_LOCK_NO## : (no lock) - The default | |
1010 | -- ## ##DB_LOCK_SHARED## : (shared lock for read-only access) | |
1011 | -- ## ##DB_LOCK_EXCLUSIVE## : (for read/write access). | |
1012 | -- | |
1013 | -- Returns: | |
1014 | -- An **integer**, status code, either ##DB_OK## if creation successful or anything else on an error. | |
1015 | -- | |
1016 | -- The return codes are: | |
1017 | -- | |
1018 | -- | |
1019 | -- public constant | |
1020 | -- DB_OK = 0 -- success | |
1021 | -- DB_OPEN_FAIL = -1 -- could not open the file | |
1022 | -- DB_LOCK_FAIL = -3 -- could not lock the file in the | |
1023 | -- -- manner requested | |
1024 | -- | |
1025 | -- | |
1026 | -- Comments: | |
1027 | -- ##DB_LOCK_SHARED## is only supported on Unix platforms. It allows you to read the database, | |
1028 | -- but not write anything to it. If you request ##DB_LOCK_SHARED## on //WIN32// it will be | |
1029 | -- treated as if you had asked for DB_LOCK_EXCLUSIVE. | |
1030 | -- | |
1031 | -- If the lock fails, your program should wait a few seconds and try again. | |
1032 | -- Another process might be currently accessing the database. | |
1033 | -- | |
1034 | -- Example 1: | |
1035 | -- | |
1036 | -- tries = 0 | |
1037 | -- while 1 do | |
1038 | -- err = db_open("mydata", DB_LOCK_SHARED) | |
1039 | -- if err = DB_OK then | |
1040 | -- exit | |
1041 | -- elsif err = DB_LOCK_FAIL then | |
1042 | -- tries += 1 | |
1043 | -- if tries > 10 then | |
1044 | -- puts(2, "too many tries, giving up\n") | |
1045 | -- abort(1) | |
1046 | -- else | |
1047 | -- sleep(5) | |
1048 | -- end if | |
1049 | -- else | |
1050 | -- puts(2, "Couldn't open the database!\n") | |
1051 | -- abort(1) | |
1052 | -- end if | |
1053 | -- end while | |
1054 | -- | |
1055 | -- | |
1056 | -- See Also: | |
1057 | -- [[:db_create]], [[:db_select]] | |
1058 | ||
1059 | 2 | |
1060 | integer db, magic | |
1061 | ||
1062 | 2 | if not eu:find('.', path) then |
1063 | 1 | path &= ".edb" |
1064 | end if | |
1065 | ||
1066 | 2 | ifdef UNIX then |
1067 | 2 | if lock_method = DB_LOCK_NO or |
1068 | lock_method = DB_LOCK_EXCLUSIVE then | |
1069 | -- get read and write access, "ub" | |
1070 | 2 | db = open(path, "ub") |
1071 | else | |
1072 | -- DB_LOCK_SHARED | |
1073 | 0 | db = open(path, "rb") |
1074 | end if | |
1075 | elsedef | |
1076 | if lock_method = DB_LOCK_SHARED then | |
1077 | lock_method = DB_LOCK_EXCLUSIVE | |
1078 | end if | |
1079 | db = open(path, "ub") | |
1080 | end ifdef | |
1081 | ||
1082 | 2 | if db = -1 then |
1083 | 0 | return DB_OPEN_FAIL |
1084 | end if | |
1085 | 2 | if lock_method = DB_LOCK_EXCLUSIVE then |
1086 | 1 | if not lock_file(db, LOCK_EXCLUSIVE, {}) then |
1087 | 0 | close(db) |
1088 | 0 | return DB_LOCK_FAIL |
1089 | end if | |
1090 | 1 | elsif lock_method = DB_LOCK_SHARED then |
1091 | 0 | if not lock_file(db, LOCK_SHARED, {}) then |
1092 | 0 | close(db) |
1093 | 0 | return DB_LOCK_FAIL |
1094 | end if | |
1095 | end if | |
1096 | 2 | magic = getc(db) |
1097 | 2 | if magic != DB_MAGIC then |
1098 | 0 | close(db) |
1099 | 0 | return DB_OPEN_FAIL |
1100 | end if | |
1101 | 2 | save_keys() |
1102 | 2 | current_db = db |
1103 | 2 | current_table_pos = -1 |
1104 | 2 | current_table_name = "" |
1105 | 2 | current_lock = lock_method |
1106 | 2 | db_names = append(db_names, path) |
1107 | 2 | db_lock_methods = append(db_lock_methods, lock_method) |
1108 | 2 | db_file_nums = append(db_file_nums, db) |
1109 | 2 | return DB_OK |
1110 | end function | |
1111 | ||
1112 | --** | |
1113 | -- Choose a new, already open, database to be the current database. | |
1114 | -- | |
1115 | -- Parameters: | |
1116 | -- # ##path## : a sequence, the path to the database to be the new current database. | |
1117 | -- # ##lock_method## : an integer. Optional locking method. | |
1118 | -- | |
1119 | -- Returns: | |
1120 | -- An **integer**, ##DB_OK## on success or an error code. | |
1121 | -- | |
1122 | -- Comments: | |
1123 | -- * Subsequent database operations will apply to this database. path is the | |
1124 | -- path of the database file as it was originally opened with ##db_open##() | |
1125 | -- or ##db_create##().\\ | |
1126 | -- * When you create (db_create) or open (db_open) a database, it automatically | |
1127 | -- becomes the current database. Use ##db_select##() when you want to switch back | |
1128 | -- and forth between open databases, perhaps to copy records from one to the | |
1129 | -- other. After selecting a new database, you should select a table within | |
1130 | -- that database using ##db_select_table##(). | |
1131 | -- * If the ##lock_method## is omitted and the database has not already been opened, | |
1132 | -- this function will fail. However, if ##lock_method## is a valid lock type for | |
1133 | -- [[:db_open]] and the database is not open yet, this function will attempt to | |
1134 | -- open it. It may still fail if the database cannot be opened. | |
1135 | -- | |
1136 | -- Example 1: | |
1137 | -- | |
1138 | -- if db_select("employees") != DB_OK then | |
1139 | -- puts(2, "Could not select employees database\n") | |
1140 | -- end if | |
1141 | -- | |
1142 | -- | |
1143 | -- Example 2: | |
1144 | -- | |
1145 | -- if db_select("customer", DB_LOCK_SHARED) != DB_OK then | |
1146 | -- puts(2, "Could not open or select Customer database\n") | |
1147 | -- end if | |
1148 | -- | |
1149 | -- | |
1150 | -- See Also: | |
1151 | -- [[:db_open]], [[:db_select]] | |
1152 | ||
1153 | 21 | |
1154 | integer index | |
1155 | ||
1156 | 21 | if not eu:find('.', path) then |
1157 | 0 | path &= ".edb" |
1158 | end if | |
1159 | ||
1160 | 21 | index = eu:find(path, db_names) |
1161 | 21 | if index = 0 then |
1162 | 0 | if lock_method = -1 then |
1163 | 0 | return DB_OPEN_FAIL |
1164 | end if | |
1165 | 0 | index = db_open(path, lock_method) |
1166 | 0 | if index != DB_OK then |
1167 | 0 | return index |
1168 | end if | |
1169 | 0 | index = eu:find(path, db_names) |
1170 | end if | |
1171 | 21 | save_keys() |
1172 | 21 | current_db = db_file_nums[index] |
1173 | 21 | current_lock = db_lock_methods[index] |
1174 | 21 | current_table_pos = -1 |
1175 | 21 | current_table_name = "" |
1176 | 21 | key_pointers = {} |
1177 | 21 | return DB_OK |
1178 | end function | |
1179 | ||
1180 | --** | |
1181 | -- Unlock and close the current database. | |
1182 | -- | |
1183 | -- Comments: | |
1184 | -- Call this procedure when you are finished with the current database. Any lock will be removed, allowing other processes to access the database file. The current database becomes undefined. | |
1185 | ||
1186 | 6 | |
1187 | -- close the current database | |
1188 | integer index | |
1189 | ||
1190 | 6 | if current_db = -1 then |
1191 | 1 | return |
1192 | end if | |
1193 | -- unlock the database | |
1194 | 5 | if current_lock then |
1195 | 2 | unlock_file(current_db, {}) |
1196 | end if | |
1197 | 5 | close(current_db) |
1198 | -- delete info for current_db | |
1199 | 5 | index = eu:find(current_db, db_file_nums) |
1200 | 5 | db_names = db_names[1..index-1] & db_names[index+1..$] |
1201 | 5 | db_file_nums = db_file_nums[1..index-1] & db_file_nums[index+1..$] |
1202 | 5 | db_lock_methods = db_lock_methods[1..index-1] & db_lock_methods[index+1..$] |
1203 | -- delete each cache entry for this database | |
1204 | 5 | for i = length(cache_index) to 1 by -1 do |
1205 | 22 | if cache_index[i][1] = current_db then |
1206 | 17 | cache_index = remove(cache_index, i) |
1207 | 17 | key_cache = remove(key_cache, i) |
1208 | end if | |
1209 | 22 | end for |
1210 | 5 | current_db = -1 |
1211 | 5 | key_pointers = {} |
1212 | 5 | end procedure |
1213 | ||
1214 | 99 | |
1215 | -- find a table, given its name | |
1216 | -- return table pointer | |
1217 | atom tables | |
1218 | atom nt | |
1219 | atom t_header, name_ptr | |
1220 | ||
1221 | 99 | safe_seek(TABLE_HEADERS) |
1222 | 99 | if length(vLastErrors) > 0 then return -1 end if |
1223 | 99 | tables = get4() |
1224 | 99 | void = seek(current_db, tables) |
1225 | 99 | nt = get4() |
1226 | 99 | t_header = tables+4 |
1227 | 99 | for i = 1 to nt do |
1228 | 274 | void = seek(current_db, t_header) |
1229 | 274 | name_ptr = get4() |
1230 | 274 | void = seek(current_db, name_ptr) |
1231 | 274 | if equal_string(name) > 0 then |
1232 | -- found it | |
1233 | 73 | return t_header |
1234 | end if | |
1235 | 201 | t_header += SIZEOF_TABLE_HEADER |
1236 | 201 | end for |
1237 | 26 | return -1 |
1238 | end function | |
1239 | ||
1240 | --** | |
1241 | --==== Managing tables | |
1242 | -- Parameters: | |
1243 | -- # ##name## : a sequence which defines the name of the new current table. | |
1244 | -- | |
1245 | -- Description: | |
1246 | -- On success, the table with name given by name becomes the current table. | |
1247 | -- | |
1248 | -- Returns: | |
1249 | -- An **integer**, either DB_OK on success or DB_OPEN_FAIL otherwise. | |
1250 | -- | |
1251 | -- Errors: | |
1252 | -- An error occurs if the current database is not defined. | |
1253 | -- | |
1254 | -- Comments: | |
1255 | -- All record-level database operations apply automatically to the current table. | |
1256 | -- | |
1257 | -- Example 1: | |
1258 | -- | |
1259 | -- if db_select_table("salary") != DB_OK then | |
1260 | -- puts(2, "Couldn't find salary table!\n") | |
1261 | -- abort(1) | |
1262 | -- end if | |
1263 | -- | |
1264 | -- | |
1265 | -- See Also: | |
1266 | -- [[:db_table_list]] | |
1267 | ||
1268 | 67 | |
1269 | -- let table with the given name be the current table | |
1270 | atom table, nkeys, index | |
1271 | atom block_ptr, block_size | |
1272 | integer blocks, k | |
1273 | ||
1274 | 67 | if equal(current_table_name, name) then |
1275 | 2 | return DB_OK |
1276 | end if | |
1277 | 65 | table = table_find(name) |
1278 | 65 | if table = -1 then |
1279 | 2 | return DB_OPEN_FAIL |
1280 | end if | |
1281 | ||
1282 | 63 | save_keys() |
1283 | ||
1284 | 63 | current_table_pos = table |
1285 | 63 | current_table_name = name |
1286 | ||
1287 | 63 | k = 0 |
1288 | 63 | if caching_option = 1 then |
1289 | 60 | k = eu:find({current_db, current_table_pos}, cache_index) |
1290 | 60 | if k != 0 then |
1291 | 38 | key_pointers = key_cache[k] |
1292 | end if | |
1293 | end if | |
1294 | 63 | if k = 0 then |
1295 | -- read in all the key pointers for the current table | |
1296 | 25 | void = seek(current_db, table+4) |
1297 | 25 | nkeys = get4() |
1298 | 25 | blocks = get4() |
1299 | 25 | index = get4() |
1300 | 25 | key_pointers = repeat(0, nkeys) |
1301 | 25 | k = 1 |
1302 | 25 | for b = 0 to blocks-1 do |
1303 | 27 | void = seek(current_db, index) |
1304 | 27 | block_size = get4() |
1305 | 27 | block_ptr = get4() |
1306 | 27 | void = seek(current_db, block_ptr) |
1307 | 27 | for j = 1 to block_size do |
1308 | 88 | key_pointers[k] = get4() |
1309 | 88 | k += 1 |
1310 | 88 | end for |
1311 | 27 | index += 8 |
1312 | 27 | end for |
1313 | end if | |
1314 | 63 | return DB_OK |
1315 | end function | |
1316 | ||
1317 | --** | |
1318 | -- Get name of currently selected table | |
1319 | -- | |
1320 | -- Parameters: | |
1321 | -- # None. | |
1322 | -- | |
1323 | -- Returns: | |
1324 | -- A **sequence**, the name of the current table. An empty string means | |
1325 | -- that no table is currently selected. | |
1326 | -- | |
1327 | -- Example 1: | |
1328 | -- | |
1329 | -- s = db_current_table() | |
1330 | -- | |
1331 | -- | |
1332 | -- See Also: | |
1333 | -- [[:db_select_table]], [[:db_table_list]] | |
1334 | ||
1335 | 10 | |
1336 | 10 | return current_table_name |
1337 | end function | |
1338 | ||
1339 | --** | |
1340 | -- Create a new table within the current database. | |
1341 | -- | |
1342 | -- Parameters: | |
1343 | -- # ##name## : a sequence, the name of the new table. | |
1344 | -- # ##init_records## : The number of records to initially reserve space for. | |
1345 | -- (Default is 50) | |
1346 | -- | |
1347 | -- Returns: | |
1348 | -- An **integer**, either DB_OK on success or DB_EXISTS_ALREADY on failure. | |
1349 | -- | |
1350 | -- Errors: | |
1351 | -- An error occurs if the current database is not defined. | |
1352 | -- | |
1353 | -- Comments: | |
1354 | -- * The supplied name must not exist already on the current database. | |
1355 | -- * The table that you create will initially have 0 records. However | |
1356 | -- it will reserve some space for a number of records, which will | |
1357 | -- improve the initial data load for the table. | |
1358 | -- * It becomes the current table. | |
1359 | -- | |
1360 | -- Example 1: | |
1361 | -- | |
1362 | -- if db_create_table("my_new_table") != DB_OK then | |
1363 | -- puts(2, "Could not create my_new_table!\n") | |
1364 | -- end if | |
1365 | -- | |
1366 | -- | |
1367 | -- See Also: | |
1368 | -- [[:db_select_table]], [[:db_table_list]] | |
1369 | ||
1370 | 23 | |
1371 | atom name_ptr, nt, tables, newtables, table, records_ptr | |
1372 | atom size, newsize, index_ptr | |
1373 | sequence remaining | |
1374 | integer init_index | |
1375 | ||
1376 | 23 | table = table_find(name) |
1377 | 23 | if table != -1 then |
1378 | 1 | return DB_EXISTS_ALREADY |
1379 | end if | |
1380 | ||
1381 | 22 | if init_records < 1 then |
1382 | 0 | init_records = 1 |
1383 | end if | |
1384 | 22 | init_index = min({init_records, INIT_INDEX}) |
1385 | ||
1386 | -- increment number of tables | |
1387 | 22 | void = seek(current_db, TABLE_HEADERS) |
1388 | 22 | tables = get4() |
1389 | 22 | void = seek(current_db, tables-4) |
1390 | 22 | size = get4() |
1391 | 22 | nt = get4()+1 |
1392 | 22 | if nt*SIZEOF_TABLE_HEADER + 8 > size then |
1393 | -- enlarge the block of table headers | |
1394 | 2 | newsize = floor(size + size / 2) |
1395 | 2 | newtables = db_allocate(newsize) |
1396 | 2 | put4(nt) |
1397 | -- copy all table headers to the new block | |
1398 | 2 | void = seek(current_db, tables+4) |
1399 | 2 | remaining = get_bytes(current_db, (nt-1)*SIZEOF_TABLE_HEADER) |
1400 | 2 | void = seek(current_db, newtables+4) |
1401 | 2 | putn(remaining) |
1402 | -- fill the rest | |
1403 | 2 | putn(repeat(0, newsize - 4 - (nt-1)*SIZEOF_TABLE_HEADER)) |
1404 | 2 | db_free(tables) |
1405 | 2 | void = seek(current_db, TABLE_HEADERS) |
1406 | 2 | put4(newtables) |
1407 | 2 | tables = newtables |
1408 | else | |
1409 | 20 | void = seek(current_db, tables) |
1410 | 20 | put4(nt) |
1411 | end if | |
1412 | ||
1413 | -- allocate initial space for 1st block of record pointers | |
1414 | 22 | records_ptr = db_allocate(init_records * 4) |
1415 | 22 | putn(repeat(0, init_records * 4)) |
1416 | ||
1417 | -- allocate initial space for the index | |
1418 | 22 | index_ptr = db_allocate(init_index * 8) |
1419 | 22 | put4(0) -- 0 records |
1420 | 22 | put4(records_ptr) -- point to 1st block |
1421 | 22 | putn(repeat(0, (init_index-1) * 8)) |
1422 | ||
1423 | -- store new table | |
1424 | 22 | name_ptr = db_allocate(length(name)+1) |
1425 | 22 | putn(name & 0) |
1426 | ||
1427 | 22 | void = seek(current_db, tables+4+(nt-1)*SIZEOF_TABLE_HEADER) |
1428 | 22 | put4(name_ptr) |
1429 | 22 | put4(0) -- start with 0 records total |
1430 | 22 | put4(1) -- start with 1 block of records in index |
1431 | 22 | put4(index_ptr) |
1432 | 22 | if db_select_table(name) then |
1433 | end if | |
1434 | 22 | return DB_OK |
1435 | end function | |
1436 | ||
1437 | --** | |
1438 | -- Delete a table in the current database. | |
1439 | -- | |
1440 | -- Parameters: | |
1441 | -- # ##name## : a sequence, the name of the table to delete. | |
1442 | -- | |
1443 | -- Errors: | |
1444 | -- An error occurs if the current database is not defined. | |
1445 | -- | |
1446 | -- Comments: | |
1447 | -- If there is no table with the name given by name, then nothing happens. | |
1448 | -- On success, all records are deleted and all space used by the table | |
1449 | -- is freed up. If the table was the current table, the current table | |
1450 | -- becomes undefined. | |
1451 | -- | |
1452 | -- See Also: | |
1453 | -- [[:db_table_list]], [[:db_select_table]], [[:db_clear_table]] | |
1454 | ||
1455 | 4 | |
1456 | -- delete an existing table and all of its records | |
1457 | atom table, tables, nt, nrecs, records_ptr, blocks | |
1458 | atom p, data_ptr, index | |
1459 | sequence remaining | |
1460 | integer k | |
1461 | ||
1462 | 4 | table = table_find(name) |
1463 | 4 | if table = -1 then |
1464 | 0 | return |
1465 | end if | |
1466 | ||
1467 | -- free the table name | |
1468 | 4 | void = seek(current_db, table) |
1469 | 4 | db_free(get4()) |
1470 | ||
1471 | 4 | void = seek(current_db, table+4) |
1472 | 4 | nrecs = get4() |
1473 | 4 | blocks = get4() |
1474 | 4 | index = get4() |
1475 | ||
1476 | -- free all the records | |
1477 | 4 | for b = 0 to blocks-1 do |
1478 | 4 | void = seek(current_db, index+b*8) |
1479 | 4 | nrecs = get4() |
1480 | 4 | records_ptr = get4() |
1481 | 4 | for r = 0 to nrecs-1 do |
1482 | 4 | void = seek(current_db, records_ptr + r*4) |
1483 | 4 | p = get4() |
1484 | 4 | void = seek(current_db, p) |
1485 | 4 | data_ptr = get4() |
1486 | 4 | db_free(data_ptr) |
1487 | 4 | db_free(p) |
1488 | 4 | end for |
1489 | -- free the block | |
1490 | 4 | db_free(records_ptr) |
1491 | 4 | end for |
1492 | ||
1493 | -- free the index | |
1494 | 4 | db_free(index) |
1495 | ||
1496 | -- get tables & number of tables | |
1497 | 4 | void = seek(current_db, TABLE_HEADERS) |
1498 | 4 | tables = get4() |
1499 | 4 | void = seek(current_db, tables) |
1500 | 4 | nt = get4() |
1501 | ||
1502 | -- shift later tables up | |
1503 | 4 | void = seek(current_db, table+SIZEOF_TABLE_HEADER) |
1504 | 4 | remaining = get_bytes(current_db, |
1505 | tables+4+nt*SIZEOF_TABLE_HEADER- | |
1506 | (table+SIZEOF_TABLE_HEADER)) | |
1507 | 4 | void = seek(current_db, table) |
1508 | 4 | putn(remaining) |
1509 | ||
1510 | -- decrement number of tables | |
1511 | 4 | nt -= 1 |
1512 | 4 | void = seek(current_db, tables) |
1513 | 4 | put4(nt) |
1514 | ||
1515 | 4 | k = eu:find({current_db, current_table_pos}, cache_index) |
1516 | 4 | if k != 0 then |
1517 | 2 | cache_index = remove(cache_index, k) |
1518 | 2 | key_cache = remove(key_cache, k) |
1519 | end if | |
1520 | 4 | if table = current_table_pos then |
1521 | 1 | current_table_pos = -1 |
1522 | 1 | current_table_name = "" |
1523 | 3 | elsif table < current_table_pos then |
1524 | 1 | current_table_pos -= SIZEOF_TABLE_HEADER |
1525 | 1 | void = seek(current_db, current_table_pos) |
1526 | 1 | data_ptr = get4() |
1527 | 1 | void = seek(current_db, data_ptr) |
1528 | 1 | current_table_name = get_string() |
1529 | end if | |
1530 | 4 | end procedure |
1531 | ||
1532 | --** | |
1533 | -- Clears a table of all its records, in the current database. | |
1534 | -- | |
1535 | -- Parameters: | |
1536 | -- # ##name## : a sequence, the name of the table to clear. | |
1537 | -- | |
1538 | -- Errors: | |
1539 | -- An error occurs if the current database is not defined. | |
1540 | -- | |
1541 | -- Comments: | |
1542 | -- If there is no table with the name given by name, then nothing happens. | |
1543 | -- On success, all records are deleted and all space used by the table | |
1544 | -- is freed up. If this is the current table, after this operation | |
1545 | -- it will still be the current table. | |
1546 | -- | |
1547 | -- See Also: | |
1548 | -- [[:db_table_list]], [[:db_select_table]], [[:db_delete_table]] | |
1549 | ||
1550 | 2 | |
1551 | -- delete all of records in the table | |
1552 | atom table, nrecs, records_ptr, blocks | |
1553 | atom p, data_ptr, index_ptr | |
1554 | integer k | |
1555 | integer init_index | |
1556 | ||
1557 | 2 | table = table_find(name) |
1558 | 2 | if table = -1 then |
1559 | 0 | return |
1560 | end if | |
1561 | ||
1562 | 2 | if init_records < 1 then |
1563 | 0 | init_records = 1 |
1564 | end if | |
1565 | 2 | init_index = min({init_records, INIT_INDEX}) |
1566 | ||
1567 | 2 | void = seek(current_db, table + 4) |
1568 | 2 | nrecs = get4() |
1569 | 2 | blocks = get4() |
1570 | 2 | index_ptr = get4() |
1571 | ||
1572 | -- free all the records | |
1573 | 2 | for b = 0 to blocks-1 do |
1574 | 14 | void = seek(current_db, index_ptr + b*8) |
1575 | 14 | nrecs = get4() |
1576 | 14 | records_ptr = get4() |
1577 | 14 | for r = 0 to nrecs-1 do |
1578 | 504 | void = seek(current_db, records_ptr + r*4) |
1579 | 504 | p = get4() |
1580 | 504 | void = seek(current_db, p) |
1581 | 504 | data_ptr = get4() |
1582 | 504 | db_free(data_ptr) |
1583 | 504 | db_free(p) |
1584 | 504 | end for |
1585 | -- free the block | |
1586 | 14 | db_free(records_ptr) |
1587 | 14 | end for |
1588 | ||
1589 | -- free the index | |
1590 | 2 | db_free(index_ptr) |
1591 | ||
1592 | -- allocate initial space for 1st block of record pointers | |
1593 | 2 | data_ptr = db_allocate(init_records * 4) |
1594 | 2 | putn(repeat(0, init_records * 4)) |
1595 | ||
1596 | -- allocate initial space for the index block | |
1597 | 2 | index_ptr = db_allocate(init_index * 8) |
1598 | 2 | put4(0) -- 0 records |
1599 | 2 | put4(data_ptr) -- point to 1st block |
1600 | 2 | putn(repeat(0, (init_index-1) * 8)) |
1601 | ||
1602 | 2 | void = seek(current_db, table + 4) |
1603 | 2 | put4(0) -- start with 0 records total |
1604 | 2 | put4(1) -- start with 1 block of records in index |
1605 | 2 | put4(index_ptr) |
1606 | ||
1607 | -- Clear cache and RAM pointers | |
1608 | 2 | k = eu:find({current_db, current_table_pos}, cache_index) |
1609 | 2 | if k != 0 then |
1610 | 0 | cache_index = remove(cache_index, k) |
1611 | 0 | key_cache = remove(key_cache, k) |
1612 | end if | |
1613 | 2 | if table = current_table_pos then |
1614 | 2 | key_pointers = {} |
1615 | end if | |
1616 | ||
1617 | 2 | end procedure |
1618 | ||
1619 | --** | |
1620 | -- Rename a table in the current database. | |
1621 | -- | |
1622 | -- Parameters: | |
1623 | -- # ##name## : a sequence, the name of the table to rename | |
1624 | -- # ##new_name## : a sequence, the new name for the table | |
1625 | -- | |
1626 | -- Errors: | |
1627 | -- * An error occurs if the current database is not defined. | |
1628 | -- * If ##name## does not exist on the current database, | |
1629 | -- or if ##new_name## does exist on the current database, | |
1630 | -- an error will occur. | |
1631 | -- | |
1632 | -- Comments: | |
1633 | -- The table to be renamed can be the current table, or some other table | |
1634 | -- in the current database. | |
1635 | -- | |
1636 | -- See Also: | |
1637 | -- [[:db_table_list]] | |
1638 | ||
1639 | 3 | |
1640 | -- rename an existing table - written by Jordah Ferguson | |
1641 | atom table, table_ptr | |
1642 | ||
1643 | 3 | table = table_find(name) |
1644 | 3 | if table = -1 then |
1645 | 1 | fatal(NO_TABLE, "source table doesn't exist", "db_rename_table", {name, new_name}) |
1646 | 1 | return |
1647 | end if | |
1648 | ||
1649 | 2 | if table_find(new_name) != -1 then |
1650 | 1 | fatal(DUP_TABLE, "target table name already exists", "db_rename_table", {name, new_name}) |
1651 | 1 | return |
1652 | end if | |
1653 | ||
1654 | 1 | void = seek(current_db, table) |
1655 | 1 | db_free(get4()) |
1656 | ||
1657 | 1 | table_ptr = db_allocate(length(new_name)+1) |
1658 | 1 | putn(new_name & 0) |
1659 | ||
1660 | 1 | void = seek(current_db, table) |
1661 | 1 | put4(table_ptr) |
1662 | 1 | end procedure |
1663 | ||
1664 | --** | |
1665 | -- Lists all tables on the current database. | |
1666 | -- | |
1667 | -- Returns: | |
1668 | -- A **sequence**, of all the table names in the current database. Each element of this | |
1669 | -- sequence is a sequence, the name of a table. | |
1670 | -- | |
1671 | -- Errors: | |
1672 | -- An error occurs if the current database is undefined. | |
1673 | -- | |
1674 | -- Example 1: | |
1675 | -- | |
1676 | -- sequence names = db_table_list() | |
1677 | -- for i = 1 to length(names) do | |
1678 | -- puts(1, names[i] & '\n') | |
1679 | -- end for | |
1680 | -- | |
1681 | -- | |
1682 | -- See Also: | |
1683 | -- [[:db_select_table]], [[:db_create_table]] | |
1684 | ||
1685 | 7 | |
1686 | sequence table_names | |
1687 | atom tables, nt, name | |
1688 | ||
1689 | 7 | safe_seek(TABLE_HEADERS) |
1690 | 7 | if length(vLastErrors) > 0 then return {} end if |
1691 | 7 | tables = get4() |
1692 | 7 | void = seek(current_db, tables) |
1693 | 7 | nt = get4() |
1694 | 7 | table_names = repeat(0, nt) |
1695 | 7 | for i = 0 to nt-1 do |
1696 | 14 | void = seek(current_db, tables + 4 + i*SIZEOF_TABLE_HEADER) |
1697 | 14 | name = get4() |
1698 | 14 | void = seek(current_db, name) |
1699 | 14 | table_names[i+1] = get_string() |
1700 | 14 | end for |
1701 | 7 | return table_names |
1702 | end function | |
1703 | ||
1704 | 5518 | |
1705 | -- return the value of a key, | |
1706 | -- given a pointer to the key in the database | |
1707 | 5518 | void = seek(current_db, ptr+4) -- skip ptr to data |
1708 | 5518 | return decompress(0) |
1709 | end function | |
1710 | ||
1711 | --**** | |
1712 | --==== Managing Records | |
1713 | ||
1714 | --** | |
1715 | -- Find the record in the current table with supplied key. | |
1716 | -- | |
1717 | -- Parameters: | |
1718 | -- # ##key## : the identifier of the record to be looked up. | |
1719 | -- # ##table_name## : optional name of table to find key in | |
1720 | -- | |
1721 | -- Returns: | |
1722 | -- An **integer**, either greater or less than zero: | |
1723 | -- * If above zero, the record identified by ##key## was found on the | |
1724 | -- current table, and the returned integer is its record number. | |
1725 | -- * If less than zero, the record was not found. The returned integer | |
1726 | -- is the opposite of what the record number would have been, had | |
1727 | -- the record been found. | |
1728 | -- * If equal to zero, an error occured. | |
1729 | -- | |
1730 | -- Errors: | |
1731 | -- If the current table is not defined, it returns 0. | |
1732 | -- | |
1733 | -- Comments: | |
1734 | -- | |
1735 | -- A fast binary search is used to find the key in the current table. | |
1736 | -- The number of comparisons is proportional to the log of the number of | |
1737 | -- records in the table. The key is unique~--a table is more like a dictionary than like a spreadsheet. | |
1738 | -- | |
1739 | -- You can select a range of records by searching | |
1740 | -- for the first and last key values in the range. If those key values don't | |
1741 | -- exist, you'll at least get a negative value showing where they would be, | |
1742 | -- if they existed. e.g. Suppose you want to know which records have keys | |
1743 | -- greater than "GGG" and less than "MMM". If -5 is returned for key "GGG", | |
1744 | -- it means a record with "GGG" as a key would be inserted as record number 5. | |
1745 | -- -27 for "MMM" means a record with "MMM" as its key would be inserted as record | |
1746 | -- number 27. This quickly tells you that all records, >= 5 and < 27 qualify. | |
1747 | -- | |
1748 | -- Example 1: | |
1749 | -- | |
1750 | -- rec_num = db_find_key("Millennium") | |
1751 | -- if rec_num > 0 then | |
1752 | -- ? db_record_key(rec_num) | |
1753 | -- ? db_record_data(rec_num) | |
1754 | -- else | |
1755 | -- puts(2, "Not found, but if you insert it,\n") | |
1756 | -- | |
1757 | -- printf(2, "it will be #%d\n", -rec_num) | |
1758 | -- end if | |
1759 | -- | |
1760 | -- | |
1761 | -- See Also: | |
1762 | -- [[:db_insert]], [[:db_replace_data]], [[:db_delete_record]], [[:db_get_recid]] | |
1763 | ||
1764 | 773 | |
1765 | integer lo, hi, mid, c -- works up to 1.07 billion records | |
1766 | ||
1767 | 773 | if not equal(table_name, current_table_name) then |
1768 | 4 | if db_select_table(table_name) != DB_OK then |
1769 | 0 | fatal(NO_TABLE, "invalid table name given", "db_find_key", {key, table_name}) |
1770 | 0 | return 0 |
1771 | end if | |
1772 | end if | |
1773 | ||
1774 | 773 | if current_table_pos = -1 then |
1775 | 0 | fatal(NO_TABLE, "no table selected", "db_find_key", {key, table_name}) |
1776 | 0 | return 0 |
1777 | end if | |
1778 | 773 | lo = 1 |
1779 | 773 | hi = length(key_pointers) |
1780 | 773 | mid = 1 |
1781 | 773 | c = 0 |
1782 | 773 | while lo <= hi do |
1783 | 5412 | mid = floor((lo + hi) / 2) |
1784 | 5412 | c = eu:compare(key, key_value(key_pointers[mid])) |
1785 | 5412 | if c < 0 then |
1786 | 151 | hi = mid - 1 |
1787 | 5261 | elsif c > 0 then |
1788 | 5224 | lo = mid + 1 |
1789 | else | |
1790 | 37 | return mid |
1791 | end if | |
1792 | 5375 | end while |
1793 | -- return the position it would have, if inserted now | |
1794 | 736 | if c > 0 then |
1795 | 700 | mid += 1 |
1796 | end if | |
1797 | 736 | return -mid |
1798 | end function | |
1799 | ||
1800 | --** | |
1801 | -- Returns the unique record identifier (##recid##) value for the record. | |
1802 | -- | |
1803 | -- Parameters: | |
1804 | -- # ##key## : the identifier of the record to be looked up. | |
1805 | -- # ##table_name## : optional name of table to find key in | |
1806 | -- | |
1807 | -- Returns: | |
1808 | -- An **atom**, either greater or equal to zero: | |
1809 | -- * If above zero, it is a ##recid##. | |
1810 | -- * If less than zero, the record wasn't found. | |
1811 | -- * If equal to zero, an error occured. | |
1812 | -- | |
1813 | -- Errors: | |
1814 | -- If the table is not defined, an error is raised. | |
1815 | -- | |
1816 | -- Comments: | |
1817 | -- A **##recid##** is a number that uniquely identifies a record in the database. | |
1818 | -- No two records in a database has the same ##recid## value. They can be used | |
1819 | -- instead of keys to //quickly// refetch a record, as they avoid the overhead of | |
1820 | -- looking for a matching record key. They can also be used without selecting | |
1821 | -- a table first, as the ##recid## is unique to the database and not just a table. | |
1822 | -- However, they only remain valid while a database is open and so long as it | |
1823 | -- doesn't get compressed. Compressing the database will give each record a | |
1824 | -- new ##recid## value. | |
1825 | -- | |
1826 | -- Because it is faster to fetch a record with a ##recid## rather than with its key, | |
1827 | -- these are used when you know you have to **refetch** a record. | |
1828 | -- | |
1829 | -- Example 1: | |
1830 | -- | |
1831 | -- rec_num = db_get_recid("Millennium") | |
1832 | -- if rec_num > 0 then | |
1833 | -- ? db_record_recid(rec_num) -- fetch key and data. | |
1834 | -- else | |
1835 | -- puts(2, "Not found\n") | |
1836 | -- end if | |
1837 | -- | |
1838 | -- | |
1839 | -- See Also: | |
1840 | -- [[:db_insert]], [[:db_replace_data]], [[:db_delete_record]], [[:db_find_key]] | |
1841 | ||
1842 | 3 | |
1843 | integer lo, hi, mid, c -- works up to 1.07 billion records | |
1844 | ||
1845 | 3 | if not equal(table_name, current_table_name) then |
1846 | 0 | if db_select_table(table_name) != DB_OK then |
1847 | 0 | fatal(NO_TABLE, "invalid table name given", "db_get_recid", {key, table_name}) |
1848 | 0 | return 0 |
1849 | end if | |
1850 | end if | |
1851 | ||
1852 | 3 | if current_table_pos = -1 then |
1853 | 0 | fatal(NO_TABLE, "no table selected", "db_get_recid", {key, table_name}) |
1854 | 0 | return 0 |
1855 | end if | |
1856 | 3 | lo = 1 |
1857 | 3 | hi = length(key_pointers) |
1858 | 3 | mid = 1 |
1859 | 3 | c = 0 |
1860 | 3 | while lo <= hi do |
1861 | 19 | mid = floor((lo + hi) / 2) |
1862 | 19 | c = eu:compare(key, key_value(key_pointers[mid])) |
1863 | 19 | if c < 0 then |
1864 | 8 | hi = mid - 1 |
1865 | 11 | elsif c > 0 then |
1866 | 9 | lo = mid + 1 |
1867 | else | |
1868 | 2 | return key_pointers[mid] |
1869 | end if | |
1870 | 17 | end while |
1871 | 1 | return -1 |
1872 | end function | |
1873 | ||
1874 | --** | |
1875 | -- Insert a new record into the current table. | |
1876 | -- | |
1877 | -- Parameters: | |
1878 | -- # ##key## : an object, the record key, which uniquely identifies it inside the current table | |
1879 | -- # ##data## : an object, associated to ##key##. | |
1880 | -- # ##table_name## : optional table name to insert record into | |
1881 | -- | |
1882 | -- Returns: | |
1883 | -- An **integer**, either DB_OK on success or an error code on failure. | |
1884 | -- | |
1885 | -- Comments: | |
1886 | -- Within a table, all keys must be unique. ##db_insert##() will fail with | |
1887 | -- ##DB_EXISTS_ALREADY## if a record already exists on current table with the same key value. | |
1888 | -- | |
1889 | -- Both key and data can be any Euphoria data objects, atoms or sequences. | |
1890 | -- | |
1891 | -- Example 1: | |
1892 | -- | |
1893 | -- if db_insert("Smith", {"Peter", 100, 34.5}) != DB_OK then | |
1894 | -- puts(2, "insert failed!\n") | |
1895 | -- end if | |
1896 | -- | |
1897 | -- | |
1898 | -- See Also: | |
1899 | -- [[:db_delete_record]] | |
1900 | ||
1901 | 707 | |
1902 | sequence key_string, data_string, last_part, remaining | |
1903 | atom key_ptr, data_ptr, records_ptr, nrecs, current_block, size, new_size | |
1904 | atom key_location, new_block, index_ptr, new_index_ptr, total_recs | |
1905 | integer r, blocks, new_recs, n | |
1906 | ||
1907 | 707 | key_location = db_find_key(key, table_name) -- Let it set the current table if necessary |
1908 | ||
1909 | 707 | if key_location > 0 then |
1910 | -- key is already in the table | |
1911 | 1 | return DB_EXISTS_ALREADY |
1912 | end if | |
1913 | 706 | key_location = -key_location |
1914 | ||
1915 | 706 | data_string = compress(data) |
1916 | 706 | key_string = compress(key) |
1917 | ||
1918 | 706 | data_ptr = db_allocate(length(data_string)) |
1919 | 706 | putn(data_string) |
1920 | ||
1921 | 706 | key_ptr = db_allocate(4+length(key_string)) |
1922 | 706 | put4(data_ptr) |
1923 | 706 | putn(key_string) |
1924 | ||
1925 | -- increment number of records in whole table | |
1926 | ||
1927 | 706 | void = seek(current_db, current_table_pos+4) |
1928 | 706 | total_recs = get4()+1 |
1929 | 706 | blocks = get4() |
1930 | 706 | void = seek(current_db, current_table_pos+4) |
1931 | 706 | put4(total_recs) |
1932 | ||
1933 | 706 | n = length(key_pointers) |
1934 | 706 | if key_location >= floor(n/2) then |
1935 | -- add space at end | |
1936 | 705 | key_pointers = append(key_pointers, 0) |
1937 | -- shift up | |
1938 | 705 | key_pointers[key_location+1..n+1] = key_pointers[key_location..n] |
1939 | else | |
1940 | -- add space at beginning | |
1941 | 1 | key_pointers = prepend(key_pointers, 0) |
1942 | -- shift down | |
1943 | 1 | key_pointers[1..key_location-1] = key_pointers[2..key_location] |
1944 | end if | |
1945 | 706 | key_pointers[key_location] = key_ptr |
1946 | ||
1947 | 706 | void = seek(current_db, current_table_pos+12) -- get after put - seek is necessary |
1948 | 706 | index_ptr = get4() |
1949 | ||
1950 | 706 | void = seek(current_db, index_ptr) |
1951 | 706 | r = 0 |
1952 | 706 | while TRUE do |
1953 | 3786 | nrecs = get4() |
1954 | 3786 | records_ptr = get4() |
1955 | 3786 | r += nrecs |
1956 | 3786 | if r + 1 >= key_location then |
1957 | 706 | exit |
1958 | end if | |
1959 | 3080 | end while |
1960 | ||
1961 | 706 | current_block = where(current_db)-8 |
1962 | ||
1963 | 706 | key_location -= (r-nrecs) |
1964 | ||
1965 | 706 | void = seek(current_db, records_ptr+4*(key_location-1)) |
1966 | 706 | for i = key_location to nrecs+1 do |
1967 | 714 | put4(key_pointers[i+r-nrecs]) |
1968 | 714 | end for |
1969 | ||
1970 | -- increment number of records in this block | |
1971 | 706 | void = seek(current_db, current_block) |
1972 | 706 | nrecs += 1 |
1973 | 706 | put4(nrecs) |
1974 | ||
1975 | -- check allocated size for this block | |
1976 | 706 | void = seek(current_db, records_ptr - 4) |
1977 | 706 | size = get4() - 4 |
1978 | 706 | if nrecs*4 > size-4 then |
1979 | -- This block is now full - split it into 2 pieces. | |
1980 | -- Magic formula: On average we'd like to have N blocks with | |
1981 | -- N records in each block and space for 2N records, | |
1982 | -- with N-squared total records in the table. We should also | |
1983 | -- avoid allocating extremely small blocks, and we should | |
1984 | -- anticipate some future growth of the database. | |
1985 | ||
1986 | 16 | new_size = 8 * (20 + floor(sqrt(1.5 * total_recs))) |
1987 | ||
1988 | 16 | new_recs = floor(new_size/8) |
1989 | 16 | if new_recs > floor(nrecs/2) then |
1990 | 16 | new_recs = floor(nrecs/2) |
1991 | end if | |
1992 | ||
1993 | -- copy last portion to the new block | |
1994 | 16 | void = seek(current_db, records_ptr + (nrecs-new_recs)*4) |
1995 | 16 | last_part = get_bytes(current_db, new_recs*4) |
1996 | 16 | new_block = db_allocate(new_size) |
1997 | 16 | putn(last_part) |
1998 | -- fill the rest | |
1999 | 16 | putn(repeat(0, new_size-length(last_part))) |
2000 | ||
2001 | -- change nrecs for this block in index | |
2002 | 16 | void = seek(current_db, current_block) |
2003 | 16 | put4(nrecs-new_recs) |
2004 | ||
2005 | -- insert new block into index after current block | |
2006 | 16 | void = seek(current_db, current_block+8) |
2007 | 16 | remaining = get_bytes(current_db, index_ptr+blocks*8-(current_block+8)) |
2008 | 16 | void = seek(current_db, current_block+8) |
2009 | 16 | put4(new_recs) |
2010 | 16 | put4(new_block) |
2011 | 16 | putn(remaining) |
2012 | 16 | void = seek(current_db, current_table_pos+8) |
2013 | 16 | blocks += 1 |
2014 | 16 | put4(blocks) |
2015 | -- enlarge index if full | |
2016 | 16 | void = seek(current_db, index_ptr-4) |
2017 | 16 | size = get4() - 4 |
2018 | 16 | if blocks*8 > size-8 then |
2019 | -- grow the index | |
2020 | 1 | remaining = get_bytes(current_db, blocks*8) |
2021 | 1 | new_size = floor(size + size/2) |
2022 | 1 | new_index_ptr = db_allocate(new_size) |
2023 | 1 | putn(remaining) |
2024 | 1 | putn(repeat(0, new_size-blocks*8)) |
2025 | 1 | db_free(index_ptr) |
2026 | 1 | void = seek(current_db, current_table_pos+12) |
2027 | 1 | put4(new_index_ptr) |
2028 | end if | |
2029 | end if | |
2030 | 706 | return DB_OK |
2031 | end function | |
2032 | ||
2033 | --** | |
2034 | -- Delete record number key_location from the current table. | |
2035 | -- | |
2036 | -- Parameter: | |
2037 | -- # ##key_location## : a positive integer, designating the record to delete. | |
2038 | -- # ##table_name## : optional table name to delete record from. | |
2039 | -- | |
2040 | -- Errors: | |
2041 | -- If the current table is not defined, or ##key_location## is not a valid record index, an error will occur. Valid record indexes are between 1 and the number of records in the table. | |
2042 | -- | |
2043 | -- Example 1: | |
2044 | -- | |
2045 | -- db_delete_record(55) | |
2046 | -- | |
2047 | -- | |
2048 | -- See Also: | |
2049 | -- [[:db_find_key]] | |
2050 | ||
2051 | 22 | |
2052 | atom key_ptr, nrecs, records_ptr, data_ptr, index_ptr, current_block | |
2053 | integer r, blocks, n | |
2054 | sequence remaining | |
2055 | ||
2056 | 22 | if not equal(table_name, current_table_name) then |
2057 | 3 | if db_select_table(table_name) != DB_OK then |
2058 | 1 | fatal(NO_TABLE, "invalid table name given", "db_delete_record", {key_location, table_name}) |
2059 | 1 | return |
2060 | end if | |
2061 | end if | |
2062 | ||
2063 | 21 | if current_table_pos = -1 then |
2064 | 1 | fatal(NO_TABLE, "no table selected", "db_delete_record", {key_location, table_name}) |
2065 | 1 | return |
2066 | end if | |
2067 | 20 | if key_location < 1 or key_location > length(key_pointers) then |
2068 | 0 | fatal(BAD_RECNO, "bad record number", "db_delete_record", {key_location, table_name}) |
2069 | 0 | return |
2070 | end if | |
2071 | 20 | key_ptr = key_pointers[key_location] |
2072 | 20 | safe_seek(key_ptr) |
2073 | 20 | if length(vLastErrors) > 0 then return end if |
2074 | 20 | data_ptr = get4() |
2075 | 20 | db_free(key_ptr) |
2076 | 20 | db_free(data_ptr) |
2077 | ||
2078 | 20 | n = length(key_pointers) |
2079 | 20 | if key_location >= floor(n/2) then |
2080 | -- shift down | |
2081 | 12 | key_pointers[key_location..n-1] = key_pointers[key_location+1..n] |
2082 | 12 | key_pointers = key_pointers[1..n-1] |
2083 | else | |
2084 | -- shift up | |
2085 | 8 | key_pointers[2..key_location] = key_pointers[1..key_location-1] |
2086 | 8 | key_pointers = key_pointers[2..n] |
2087 | end if | |
2088 | ||
2089 | -- decrement number of records in whole table | |
2090 | 20 | void = seek(current_db, current_table_pos+4) |
2091 | 20 | nrecs = get4()-1 |
2092 | 20 | blocks = get4() |
2093 | 20 | void = seek(current_db, current_table_pos+4) |
2094 | 20 | put4(nrecs) |
2095 | ||
2096 | 20 | void = seek(current_db, current_table_pos+12) |
2097 | 20 | index_ptr = get4() |
2098 | ||
2099 | 20 | void = seek(current_db, index_ptr) |
2100 | 20 | r = 0 |
2101 | 20 | while TRUE do |
2102 | 36 | nrecs = get4() |
2103 | 36 | records_ptr = get4() |
2104 | 36 | r += nrecs |
2105 | 36 | if r >= key_location then |
2106 | 20 | exit |
2107 | end if | |
2108 | 16 | end while |
2109 | ||
2110 | 20 | r -= nrecs |
2111 | 20 | current_block = where(current_db)-8 |
2112 | 20 | nrecs -= 1 |
2113 | ||
2114 | 20 | if nrecs = 0 and blocks > 1 then |
2115 | -- delete this block from the index (unless it's the very last block) | |
2116 | 0 | remaining = get_bytes(current_db, index_ptr+blocks*8-(current_block+8)) |
2117 | 0 | void = seek(current_db, current_block) |
2118 | 0 | putn(remaining) |
2119 | 0 | void = seek(current_db, current_table_pos+8) |
2120 | 0 | put4(blocks-1) |
2121 | 0 | db_free(records_ptr) |
2122 | else | |
2123 | 20 | key_location -= r |
2124 | -- decrement the record count in the index | |
2125 | 20 | void = seek(current_db, current_block) |
2126 | 20 | put4(nrecs) |
2127 | -- delete one record | |
2128 | 20 | void = seek(current_db, records_ptr+4*(key_location-1)) |
2129 | 20 | for i = key_location to nrecs do |
2130 | 235 | put4(key_pointers[i+r]) |
2131 | 235 | end for |
2132 | end if | |
2133 | 20 | end procedure |
2134 | ||
2135 | --** | |
2136 | -- In the current database, replace the data portion of a record with new data. | |
2137 | -- This can be used to quickly update records that have already been located | |
2138 | -- by calling [[:db_get_recid]]. This operation is faster than using | |
2139 | -- [[:db_replace_data]] | |
2140 | -- | |
2141 | -- Parameters: | |
2142 | -- # ##recid## : an atom, the ##recid## of the record to be updated. | |
2143 | -- # ##data## : an object, the new value of the record. | |
2144 | -- | |
2145 | -- Comments: | |
2146 | -- * ##recid## must be fetched using [[:db_get_recid]] first. | |
2147 | -- * ##data## is an Euphoria object of any kind, atom or sequence. | |
2148 | -- * The ##recid## does not have to be from the current table. | |
2149 | -- * This does no error checking. It assumes the database is open and valid. | |
2150 | -- | |
2151 | -- Example 1: | |
2152 | -- | |
2153 | -- rid = db_get_recid("Peter") | |
2154 | -- rec = db_record_recid(rid) | |
2155 | -- rec[2][3] *= 1.10 | |
2156 | -- db_replace_recid(rid, rec[2]) | |
2157 | -- | |
2158 | -- | |
2159 | -- See Also: | |
2160 | -- [[:db_replace_data]], [[:db_find_key]], [[:db_get_recid]] | |
2161 | ||
2162 | 3 | |
2163 | atom old_size, new_size, data_ptr | |
2164 | sequence data_string | |
2165 | ||
2166 | 3 | void = seek(current_db, recid) |
2167 | 3 | data_ptr = get4() |
2168 | 3 | void = seek(current_db, data_ptr-4) |
2169 | 3 | old_size = get4()-4 |
2170 | 3 | data_string = compress(data) |
2171 | 3 | new_size = length(data_string) |
2172 | 3 | if new_size <= old_size and |
2173 | new_size >= old_size - 16 then | |
2174 | -- keep the same data block | |
2175 | 1 | void = seek(current_db, data_ptr) |
2176 | else | |
2177 | -- free the old block | |
2178 | 2 | db_free(data_ptr) |
2179 | -- get a new data block | |
2180 | 2 | data_ptr = db_allocate(new_size + 8) |
2181 | 2 | void = seek(current_db, recid) |
2182 | 2 | put4(data_ptr) |
2183 | 2 | void = seek(current_db, data_ptr) |
2184 | ||
2185 | -- if the data comes from the end of the file, we need to | |
2186 | -- make sure it gets filled | |
2187 | 2 | data_string &= repeat( 0, 8 ) |
2188 | ||
2189 | end if | |
2190 | 3 | putn(data_string) |
2191 | 3 | end procedure |
2192 | ||
2193 | --** | |
2194 | -- In the current table, replace the data portion of a record with new data. | |
2195 | -- | |
2196 | -- Parameters: | |
2197 | -- # ##key_location##: an integer, the index of the record the data is to be altered. | |
2198 | -- # ##data##: an object , the new value associated to the key of the record. | |
2199 | -- # ##table_name##: optional table name of record to replace data in. | |
2200 | -- | |
2201 | -- Comments: | |
2202 | --##key_location## must be from 1 to the number of records in the | |
2203 | -- current table. | |
2204 | -- ##data## is an Euphoria object of any kind, atom or sequence. | |
2205 | -- | |
2206 | -- Example 1: | |
2207 | -- | |
2208 | -- db_replace_data(67, {"Peter", 150, 34.5}) | |
2209 | -- | |
2210 | -- | |
2211 | -- See Also: | |
2212 | -- [[:db_find_key]] | |
2213 | ||
2214 | 2 | |
2215 | 2 | if not equal(table_name, current_table_name) then |
2216 | 2 | if db_select_table(table_name) != DB_OK then |
2217 | 0 | fatal(NO_TABLE, "invalid table name given", "db_replace_data", {key_location, data, table_name}) |
2218 | 0 | return |
2219 | end if | |
2220 | end if | |
2221 | ||
2222 | 2 | if current_table_pos = -1 then |
2223 | 0 | fatal(NO_TABLE, "no table selected", "db_replace_data", {key_location, data, table_name}) |
2224 | 0 | return |
2225 | end if | |
2226 | 2 | if key_location < 1 or key_location > length(key_pointers) then |
2227 | 0 | fatal(BAD_RECNO, "bad record number", "db_replace_data", {key_location, data, table_name}) |
2228 | 0 | return |
2229 | end if | |
2230 | 2 | db_replace_recid(key_pointers[key_location], data) |
2231 | 2 | end procedure |
2232 | ||
2233 | --** | |
2234 | -- Get the size (number of records) of the default table. | |
2235 | -- | |
2236 | -- Parameters: | |
2237 | -- # ##table_name## : optional table name to get the size of. | |
2238 | -- | |
2239 | -- Returns | |
2240 | -- An **integer**, the current number of records in the current table. | |
2241 | -- If a value less than zero is returned, it means that an error occured. | |
2242 | -- | |
2243 | -- Errors: | |
2244 | -- If the current table is undefined, an error will occur. | |
2245 | -- | |
2246 | -- Example 1: | |
2247 | -- | |
2248 | -- -- look at all records in the current table | |
2249 | -- for i = 1 to db_table_size() do | |
2250 | -- if db_record_key(i) = 0 then | |
2251 | -- puts(1, "0 key found\n") | |
2252 | -- exit | |
2253 | -- end if | |
2254 | -- end for | |
2255 | -- | |
2256 | -- | |
2257 | -- See Also: | |
2258 | -- [[:db_replace_data]] | |
2259 | ||
2260 | 12 | |
2261 | 12 | if not equal(table_name, current_table_name) then |
2262 | 3 | if db_select_table(table_name) != DB_OK then |
2263 | 0 | fatal(NO_TABLE, "invalid table name given", "db_table_size", {table_name}) |
2264 | 0 | return -1 |
2265 | end if | |
2266 | end if | |
2267 | ||
2268 | 12 | if current_table_pos = -1 then |
2269 | 0 | fatal(NO_TABLE, "no table selected", "db_table_size", {table_name}) |
2270 | 0 | return -1 |
2271 | end if | |
2272 | 12 | return length(key_pointers) |
2273 | end function | |
2274 | ||
2275 | --** | |
2276 | -- Returns the data in a record queried by position. | |
2277 | -- | |
2278 | -- Parameters: | |
2279 | -- # ##key_location## : the index of the record the data of which is being fetched. | |
2280 | -- # ##table_name## : optional table name to get record data from. | |
2281 | -- | |
2282 | -- Returns: | |
2283 | -- An **object**, the data portion of requested record.\\ | |
2284 | -- **NOTE** This function calls ##fatal##() and returns a value of -1 if an error prevented | |
2285 | -- the correct data being returned. | |
2286 | -- | |
2287 | -- Comments: | |
2288 | -- Each record in a Euphoria database consists of a key portion and a data | |
2289 | -- portion. Each of these can be any Euphoria atom or sequence. | |
2290 | -- | |
2291 | -- Errors: | |
2292 | -- If the current table is not defined, or if the record index is invalid, an error will occur. | |
2293 | -- | |
2294 | -- Example 1: | |
2295 | -- | |
2296 | -- puts(1, "The 6th record has data value: ") | |
2297 | -- ? db_record_data(6) | |
2298 | -- | |
2299 | -- | |
2300 | -- See Also: | |
2301 | -- [[:db_find_key]], [[:db_replace_data]] | |
2302 | ||
2303 | 92 | |
2304 | atom data_ptr | |
2305 | object data_value | |
2306 | ||
2307 | 92 | if not equal(table_name, current_table_name) then |
2308 | 2 | if db_select_table(table_name) != DB_OK then |
2309 | 0 | fatal(NO_TABLE, "invalid table name given", "db_record_data", {key_location, table_name}) |
2310 | 0 | return -1 |
2311 | end if | |
2312 | end if | |
2313 | ||
2314 | 92 | if current_table_pos = -1 then |
2315 | 0 | fatal(NO_TABLE, "no table selected", "db_record_data", {key_location, table_name}) |
2316 | 0 | return -1 |
2317 | end if | |
2318 | 92 | if key_location < 1 or key_location > length(key_pointers) then |
2319 | 1 | fatal(BAD_RECNO, "bad record number", "db_record_data", {key_location, table_name}) |
2320 | 1 | return -1 |
2321 | end if | |
2322 | ||
2323 | 91 | safe_seek(key_pointers[key_location]) |
2324 | 91 | if length(vLastErrors) > 0 then return -1 end if |
2325 | 91 | data_ptr = get4() |
2326 | 91 | void = seek(current_db, data_ptr) |
2327 | 91 | data_value = decompress(0) |
2328 | ||
2329 | 91 | return data_value |
2330 | end function | |
2331 | ||
2332 | --** | |
2333 | -- Returns the data for the record with supplied key. | |
2334 | -- | |
2335 | -- Parameters: | |
2336 | -- # ##key## : the identifier of the record to be looked up. | |
2337 | -- # ##table_name## : optional name of table to find key in | |
2338 | -- | |
2339 | -- Returns: | |
2340 | -- An **integer**, | |
2341 | -- * If less than zero, the record was not found. The returned integer | |
2342 | -- is the opposite of what the record number would have been, had | |
2343 | -- the record been found. | |
2344 | -- * If equal to zero, an error occured. | |
2345 | -- A sequence, the data for the record. | |
2346 | -- | |
2347 | -- Errors: | |
2348 | -- If the current table is not defined, it returns 0. | |
2349 | -- | |
2350 | -- Comments: | |
2351 | -- Each record in a Euphoria database consists of a key portion and a data | |
2352 | -- portion. Each of these can be any Euphoria atom or sequence. **NOTE** This | |
2353 | -- function does not support records that data consists of a single non-sequence value. | |
2354 | -- In those cases you will need to use [[:db_find_key]] and [[:db_record_data]]. | |
2355 | -- | |
2356 | -- Example 1: | |
2357 | -- | |
2358 | -- printf(1, "The record['%s'] has data value:\n", {"foo"}) | |
2359 | -- ? db_fetch_record("foo") | |
2360 | -- | |
2361 | -- | |
2362 | -- See Also: | |
2363 | -- [[:db_find_key]], [[:db_record_data]] | |
2364 | ||
2365 | 0 | |
2366 | integer pos | |
2367 | ||
2368 | 0 | pos = db_find_key(key, table_name) |
2369 | 0 | if pos > 0 then |
2370 | 0 | return db_record_data(pos, table_name) |
2371 | else | |
2372 | 0 | return pos |
2373 | end if | |
2374 | end function | |
2375 | ||
2376 | --** | |
2377 | -- Parameters: | |
2378 | -- # ##key_location## : an integer, the index of the record the key is being requested. | |
2379 | -- # ##table_name## : optional table name to get record key from. | |
2380 | -- | |
2381 | -- Returns | |
2382 | -- An **object**, the key of the record being queried by index.\\ | |
2383 | -- **NOTE** This function calls fatal() and returns a value of -1 if an error prevented | |
2384 | -- the correct data being returned. | |
2385 | -- | |
2386 | -- Errors: | |
2387 | -- If the current table is not defined, or if the record index is invalid, an error will occur. | |
2388 | -- | |
2389 | -- Comments: | |
2390 | -- Each record in a Euphoria database consists of a key portion and a | |
2391 | -- data portion. Each of these can be any Euphoria atom or sequence. | |
2392 | -- | |
2393 | -- Example 1: | |
2394 | -- | |
2395 | -- puts(1, "The 6th record has key value: ") | |
2396 | -- ? db_record_key(6) | |
2397 | -- | |
2398 | -- See Also: | |
2399 | -- [[:db_record_data]] | |
2400 | ||
2401 | 87 | |
2402 | 87 | if not equal(table_name, current_table_name) then |
2403 | 0 | if db_select_table(table_name) != DB_OK then |
2404 | 0 | fatal(NO_TABLE, "invalid table name given", "db_record_key", {key_location, table_name}) |
2405 | 0 | return -1 |
2406 | end if | |
2407 | end if | |
2408 | ||
2409 | 87 | if current_table_pos = -1 then |
2410 | 0 | fatal(NO_TABLE, "no table selected", "db_record_key", {key_location, table_name}) |
2411 | 0 | return -1 |
2412 | end if | |
2413 | 87 | if key_location < 1 or key_location > length(key_pointers) then |
2414 | 0 | fatal(BAD_RECNO, "bad record number", "db_record_key", {key_location, table_name}) |
2415 | 0 | return -1 |
2416 | end if | |
2417 | 87 | return key_value(key_pointers[key_location]) |
2418 | end function | |
2419 | ||
2420 | --** | |
2421 | -- Returns the key and data in a record queried by ##recid##. | |
2422 | -- | |
2423 | -- Parameters: | |
2424 | -- # ##recid## : the ##recid## of the required record, which has been | |
2425 | -- previously fetched using [[:db_get_recid]]. | |
2426 | -- | |
2427 | -- Returns: | |
2428 | -- An **sequence**, the first element is the key and the second element | |
2429 | -- is the data portion of requested record. | |
2430 | -- | |
2431 | -- Comments: | |
2432 | -- * This is much faster than calling [[:db_record_key]] and [[:db_record_data]]. | |
2433 | -- * This does no error checking. It assumes the database is open and valid. | |
2434 | -- * This function does not need the requested record to be from the current | |
2435 | -- table. The ##recid## can refer to a record in any table. | |
2436 | -- | |
2437 | -- Example 1: | |
2438 | -- | |
2439 | -- rid = db_get_recid("SomeKey") | |
2440 | -- ? db_record_recid(rid) | |
2441 | -- | |
2442 | -- | |
2443 | -- See Also: | |
2444 | -- [[:db_get_recid]], [[:db_replace_recid]] | |
2445 | ||
2446 | 3 | |
2447 | atom data_ptr | |
2448 | object data_value | |
2449 | object key_value | |
2450 | ||
2451 | 3 | void = seek(current_db, recid) |
2452 | 3 | data_ptr = get4() |
2453 | 3 | key_value = decompress(0) |
2454 | 3 | void = seek(current_db, data_ptr) |
2455 | 3 | data_value = decompress(0) |
2456 | ||
2457 | 3 | return {key_value, data_value} |
2458 | end function | |
2459 | ||
2460 | --** | |
2461 | -- Compresses the current database. | |
2462 | -- | |
2463 | -- Returns: | |
2464 | -- An **integer**, either DB_OK on success or an error code on failure. | |
2465 | -- | |
2466 | -- Comments: | |
2467 | -- The current database is copied to a new | |
2468 | -- file such that any blocks of unused space are eliminated. If successful, | |
2469 | -- the return value will be set to ##DB_OK##, and the new compressed database | |
2470 | -- file will retain the same name. The current table will be undefined. As | |
2471 | -- a backup, the original, uncompressed file will be renamed with an extension | |
2472 | -- of .t0 (or .t1, .t2, ..., .t99). In the highly unusual case that the | |
2473 | -- compression is unsuccessful, the database will be left unchanged, and no | |
2474 | -- backup will be made. | |
2475 | -- | |
2476 | -- When you delete items from a database, you create blocks of free space within | |
2477 | -- the database file. The system keeps track of these blocks and tries to use them | |
2478 | -- for storing new data that you insert. db_compress() will copy the current | |
2479 | -- database without copying these free areas. The size of the database file may | |
2480 | -- therefore be reduced. If the backup filenames reach .t99 you will have to | |
2481 | -- delete some of them. | |
2482 | -- | |
2483 | -- Example 1: | |
2484 | -- | |
2485 | -- if db_compress() != DB_OK then | |
2486 | -- puts(2, "compress failed!\n") | |
2487 | -- end if | |
2488 | -- | |
2489 | ||
2490 | 1 | |
2491 | integer index, chunk_size, nrecs, r, fn | |
2492 | sequence new_path, old_path, table_list, record, chunk | |
2493 | ||
2494 | 1 | if current_db = -1 then |
2495 | 0 | fatal(NO_DATABASE, "no current database", "db_compress", {}) |
2496 | 0 | return -1 |
2497 | end if | |
2498 | ||
2499 | 1 | index = eu:find(current_db, db_file_nums) |
2500 | 1 | new_path = trim(db_names[index]) |
2501 | 1 | db_close() |
2502 | ||
2503 | 1 | fn = -1 |
2504 | 1 | sequence temp_path = temp_file() |
2505 | 1 | fn = open( temp_path, "r" ) |
2506 | 1 | if fn != -1 then |
2507 | 0 | return DB_EXISTS_ALREADY -- you better delete some temp files |
2508 | end if | |
2509 | ||
2510 | 1 | move_file( new_path, temp_path ) |
2511 | ||
2512 | -- create a new database | |
2513 | 1 | index = db_create(new_path, DB_LOCK_NO) |
2514 | 1 | if index != DB_OK then |
2515 | 0 | move_file( temp_path, new_path ) |
2516 | 0 | return index |
2517 | end if | |
2518 | ||
2519 | 1 | index = db_open(temp_path, DB_LOCK_NO) |
2520 | 1 | table_list = db_table_list() |
2521 | ||
2522 | 1 | for i = 1 to length(table_list) do |
2523 | 2 | index = db_select(new_path) |
2524 | 2 | index = db_create_table(table_list[i]) |
2525 | ||
2526 | 2 | index = db_select(temp_path) |
2527 | 2 | index = db_select_table(table_list[i]) |
2528 | ||
2529 | 2 | nrecs = db_table_size() |
2530 | 2 | r = 1 |
2531 | 2 | while r <= nrecs do |
2532 | 6 | chunk_size = nrecs - r + 1 |
2533 | 6 | if chunk_size > 20 then |
2534 | 4 | chunk_size = 20 -- copy up to 20 records at a time |
2535 | end if | |
2536 | -- read a bunch of records | |
2537 | 6 | chunk = {} |
2538 | 6 | for j = 1 to chunk_size do |
2539 | 87 | record = {db_record_key(r), db_record_data(r)} |
2540 | 87 | r += 1 |
2541 | 87 | chunk = append(chunk, record) |
2542 | 87 | end for |
2543 | -- switch to new table | |
2544 | 6 | index = db_select(new_path) |
2545 | 6 | index = db_select_table(table_list[i]) |
2546 | -- insert a bunch of records | |
2547 | 6 | for j = 1 to chunk_size do |
2548 | 87 | if db_insert(chunk[j][1], chunk[j][2]) != DB_OK then |
2549 | 0 | fatal(INSERT_FAILED, "couldn't insert into new database", "db_compress", {}) |
2550 | 0 | return DB_FATAL_FAIL |
2551 | end if | |
2552 | 87 | end for |
2553 | -- switch back to old table | |
2554 | 6 | index = db_select(temp_path) |
2555 | 6 | index = db_select_table(table_list[i]) |
2556 | 6 | end while |
2557 | 2 | end for |
2558 | 1 | db_close() |
2559 | 1 | index = db_select(new_path) |
2560 | 1 | return DB_OK |
2561 | end function | |
2562 | ||
2563 | --** | |
2564 | -- Get name of currently selected database. | |
2565 | -- | |
2566 | -- Parameters: | |
2567 | -- # None. | |
2568 | -- | |
2569 | -- Returns: | |
2570 | -- A **sequence**, the name of the current database. An empty string means | |
2571 | -- that no database is currently selected. | |
2572 | -- | |
2573 | -- Comments: | |
2574 | -- The actual name returned is the //path// as supplied to the db_open routine. | |
2575 | -- | |
2576 | -- Example 1: | |
2577 | -- | |
2578 | -- s = db_current_database() | |
2579 | -- | |
2580 | -- | |
2581 | -- See Also: | |
2582 | -- [[:db_select]] | |
2583 | ||
2584 | 6 | |
2585 | integer index | |
2586 | ||
2587 | 6 | index = find (current_db, db_file_nums) |
2588 | 6 | if index != 0 then |
2589 | 3 | return db_names [index] |
2590 | else | |
2591 | 3 | return "" |
2592 | end if | |
2593 | end function | |
2594 | ||
2595 | --** | |
2596 | -- Forces the database index cache to be cleared. | |
2597 | -- | |
2598 | -- Parameters: | |
2599 | -- None | |
2600 | -- | |
2601 | -- Comments: | |
2602 | -- * This is not normally required to the run. You might run it to set up a | |
2603 | -- predetermined state for performance timing, or to release some memory back to the | |
2604 | -- application. | |
2605 | -- | |
2606 | -- Example 1: | |
2607 | -- | |
2608 | -- db_cache_clear() -- Clear the cache. | |
2609 | -- | |
2610 | ||
2611 | 1 | |
2612 | 1 | cache_index = {} |
2613 | 1 | key_cache = {} |
2614 | 1 | end procedure |
2615 | ||
2616 | --** | |
2617 | -- Sets the key cache behavior.\\ | |
2618 | -- Initially, the cache option is turned on. This means that when possible, the | |
2619 | -- keys of a table are kept in RAM rather than read from disk each time | |
2620 | -- ##db_select_table##() is called. For most databases, this will improve performance | |
2621 | -- when you have more than one table in it. | |
2622 | -- | |
2623 | -- Parameters: | |
2624 | -- # ##integer## : 0 will turn of caching, 1 will turn it back on. | |
2625 | -- | |
2626 | -- Returns: | |
2627 | -- An **integer**, the previous setting of the option. | |
2628 | -- | |
2629 | -- Comments: | |
2630 | -- When caching is turned off, the current cache contents is totally cleared. | |
2631 | -- | |
2632 | -- Example 1: | |
2633 | -- | |
2634 | -- x = db_set_caching(0) -- Turn off key caching. | |
2635 | -- | |
2636 | ||
2637 | 2 | |
2638 | integer lOldVal | |
2639 | ||
2640 | 2 | lOldVal = caching_option |
2641 | 2 | caching_option = (new_setting != 0) |
2642 | ||
2643 | 2 | if caching_option = 0 then |
2644 | -- Wipe existing cache data. | |
2645 | 1 | db_cache_clear() |
2646 | end if | |
2647 | 2 | return lOldVal |
2648 | end function |