libsql 0.1.0-x64-mingw32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +60 -0
- data/HISTORY.md +6 -0
- data/LICENSE +31 -0
- data/Manifest.txt +96 -0
- data/README.md +59 -0
- data/Rakefile +28 -0
- data/TODO.md +57 -0
- data/examples/a.rb +9 -0
- data/examples/blob.rb +106 -0
- data/examples/define_aggregate.rb +75 -0
- data/examples/define_function.rb +104 -0
- data/examples/fts5.rb +152 -0
- data/examples/gem-db.rb +94 -0
- data/examples/schema-info.rb +34 -0
- data/ext/libsql/c/extconf.rb +86 -0
- data/ext/libsql/c/gen_constants.rb +353 -0
- data/ext/libsql/c/libsql_blob.c +240 -0
- data/ext/libsql/c/libsql_constants.c +1518 -0
- data/ext/libsql/c/libsql_database.c +1188 -0
- data/ext/libsql/c/libsql_ext.c +383 -0
- data/ext/libsql/c/libsql_ext.h +149 -0
- data/ext/libsql/c/libsql_statement.c +649 -0
- data/ext/libsql/c/notes.txt +134 -0
- data/ext/libsql/c/sqlite3.c +247030 -0
- data/ext/libsql/c/sqlite3.h +13436 -0
- data/lib/libsql/aggregate.rb +73 -0
- data/lib/libsql/blob.rb +186 -0
- data/lib/libsql/boolean.rb +42 -0
- data/lib/libsql/busy_timeout.rb +47 -0
- data/lib/libsql/column.rb +99 -0
- data/lib/libsql/csv_table_importer.rb +75 -0
- data/lib/libsql/database.rb +933 -0
- data/lib/libsql/function.rb +61 -0
- data/lib/libsql/index.rb +43 -0
- data/lib/libsql/libsql_ext.so +0 -0
- data/lib/libsql/memory_database.rb +15 -0
- data/lib/libsql/paths.rb +80 -0
- data/lib/libsql/profile_tap.rb +131 -0
- data/lib/libsql/progress_handler.rb +21 -0
- data/lib/libsql/schema.rb +225 -0
- data/lib/libsql/sqlite3/constants.rb +95 -0
- data/lib/libsql/sqlite3/database/function.rb +48 -0
- data/lib/libsql/sqlite3/database/status.rb +68 -0
- data/lib/libsql/sqlite3/libsql_version.rb +32 -0
- data/lib/libsql/sqlite3/status.rb +60 -0
- data/lib/libsql/sqlite3/version.rb +55 -0
- data/lib/libsql/sqlite3.rb +7 -0
- data/lib/libsql/statement.rb +421 -0
- data/lib/libsql/table.rb +91 -0
- data/lib/libsql/taps/console.rb +27 -0
- data/lib/libsql/taps/io.rb +74 -0
- data/lib/libsql/taps.rb +2 -0
- data/lib/libsql/trace_tap.rb +35 -0
- data/lib/libsql/type_map.rb +63 -0
- data/lib/libsql/type_maps/default_map.rb +166 -0
- data/lib/libsql/type_maps/storage_map.rb +38 -0
- data/lib/libsql/type_maps/text_map.rb +21 -0
- data/lib/libsql/version.rb +8 -0
- data/lib/libsql/view.rb +26 -0
- data/lib/libsql-ruby.rb +1 -0
- data/lib/libsql.rb +51 -0
- data/spec/aggregate_spec.rb +158 -0
- data/spec/blob_spec.rb +78 -0
- data/spec/boolean_spec.rb +24 -0
- data/spec/busy_handler.rb +157 -0
- data/spec/data/iso-3166-country.txt +242 -0
- data/spec/data/iso-3166-schema.sql +22 -0
- data/spec/data/iso-3166-subcountry.txt +3995 -0
- data/spec/data/make-iso-db.sh +12 -0
- data/spec/database_spec.rb +505 -0
- data/spec/default_map_spec.rb +92 -0
- data/spec/function_spec.rb +78 -0
- data/spec/integeration_spec.rb +97 -0
- data/spec/iso_3166_database.rb +58 -0
- data/spec/json_spec.rb +24 -0
- data/spec/libsql_spec.rb +4 -0
- data/spec/paths_spec.rb +28 -0
- data/spec/progress_handler_spec.rb +91 -0
- data/spec/rtree_spec.rb +66 -0
- data/spec/schema_spec.rb +131 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/sqlite3/constants_spec.rb +108 -0
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/libsql_version_spec.rb +16 -0
- data/spec/sqlite3/status_spec.rb +22 -0
- data/spec/sqlite3/version_spec.rb +28 -0
- data/spec/sqlite3_spec.rb +53 -0
- data/spec/statement_spec.rb +168 -0
- data/spec/storage_map_spec.rb +38 -0
- data/spec/tap_spec.rb +57 -0
- data/spec/text_map_spec.rb +20 -0
- data/spec/type_map_spec.rb +14 -0
- data/spec/version_spec.rb +8 -0
- data/tasks/custom.rake +134 -0
- data/tasks/default.rake +257 -0
- data/tasks/extension.rake +29 -0
- data/tasks/this.rb +208 -0
- metadata +328 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'libsql/sqlite3/database/function'
|
2
|
+
module ::Libsql
|
3
|
+
#
|
4
|
+
# A Base class to inherit from for creating your own SQL scalar functions
|
5
|
+
# in ruby.
|
6
|
+
#
|
7
|
+
# These are SQL functions similar to _abs(X)_, _length(X)_, _random()_. Items
|
8
|
+
# that take parameters and return value. They have no state between
|
9
|
+
# calls. Built in SQLite scalar functions are :
|
10
|
+
#
|
11
|
+
# * http://www.sqlite.org/lang_corefunc.html
|
12
|
+
# * http://www.sqlite.org/lang_datefunc.html
|
13
|
+
#
|
14
|
+
# Functions defined in ::Libsql databases conform to the Proc interface.
|
15
|
+
# Everything that is defined in an ::Libsql database using +define_function+
|
16
|
+
# has its +to_proc+ method called. As a result, any Function must also
|
17
|
+
# conform to the +to_proc+ protocol.
|
18
|
+
#
|
19
|
+
# If you choose to use Function as a parent class of your SQL scalar function
|
20
|
+
# implementation you should only have implement +call+ with the appropriate
|
21
|
+
# _arity_.
|
22
|
+
#
|
23
|
+
# For instance to implement a _sha1(X)_ SQL function you could implement it as
|
24
|
+
#
|
25
|
+
# class SQLSha1 < ::Libsql::Function
|
26
|
+
# def initialize
|
27
|
+
# super( 'md5', 1 )
|
28
|
+
# end
|
29
|
+
# def call( s )
|
30
|
+
# ::Digest::MD5.hexdigest( s.to_s )
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
class Function
|
35
|
+
# The name of the SQL function
|
36
|
+
attr_accessor :name
|
37
|
+
|
38
|
+
# The arity of the SQL function
|
39
|
+
attr_accessor :arity
|
40
|
+
|
41
|
+
# Initialize the function with a name and arity
|
42
|
+
def initialize( name, arity )
|
43
|
+
@name = name
|
44
|
+
@arity = arity
|
45
|
+
end
|
46
|
+
|
47
|
+
# All SQL functions defined foloow the +to_proc+ protocol
|
48
|
+
def to_proc
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# <b>Do Not Override</b>
|
53
|
+
#
|
54
|
+
# The function signature for use by the Amaglaite datase in tracking
|
55
|
+
# function definition and removal.
|
56
|
+
#
|
57
|
+
def signature
|
58
|
+
@signature ||= ::Libsql::SQLite3::Database::Function.signature( self.name, self.arity )
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/libsql/index.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2023 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module ::Libsql
|
7
|
+
#
|
8
|
+
# a class representing the meta information about an SQLite index
|
9
|
+
#
|
10
|
+
class Index
|
11
|
+
# the name of the index
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
# the sql statement that created the index
|
15
|
+
attr_reader :sql
|
16
|
+
|
17
|
+
# the table the index is for
|
18
|
+
attr_accessor :table
|
19
|
+
|
20
|
+
# the columns that make up this index, in index order
|
21
|
+
attr_accessor :columns
|
22
|
+
|
23
|
+
# sqlite sequence number of the index
|
24
|
+
attr_accessor :sequence_number
|
25
|
+
|
26
|
+
# is the index unique
|
27
|
+
attr_writer :unique
|
28
|
+
|
29
|
+
def initialize( name, sql, table )
|
30
|
+
@name = name
|
31
|
+
@sql = sql
|
32
|
+
@table = table
|
33
|
+
@columns = []
|
34
|
+
@sequence_number = nil
|
35
|
+
@unique = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def unique?
|
39
|
+
return @unique
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
Binary file
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'libsql/database'
|
2
|
+
module ::Libsql
|
3
|
+
#
|
4
|
+
# The encapsulation of a connection to an SQLite3 in-memory database.
|
5
|
+
#
|
6
|
+
# Open an in-memory database:
|
7
|
+
#
|
8
|
+
# db = ::Libsql::MemoryDatabase.new
|
9
|
+
#
|
10
|
+
class MemoryDatabase < Database
|
11
|
+
def initialize
|
12
|
+
super( ":memory:" )
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/libsql/paths.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2023 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
module ::Libsql
|
6
|
+
#
|
7
|
+
# Paths contains helpful methods to determine paths of files inside the
|
8
|
+
# ::Libsql library
|
9
|
+
#
|
10
|
+
module Paths
|
11
|
+
#
|
12
|
+
# The root directory of the project is considered to be the parent directory
|
13
|
+
# of the 'lib' directory.
|
14
|
+
#
|
15
|
+
# returns:: [String] The full expanded path of the parent directory of 'lib'
|
16
|
+
# going up the path from the current file. Trailing
|
17
|
+
# File::SEPARATOR is guaranteed.
|
18
|
+
#
|
19
|
+
def self.root_dir
|
20
|
+
@root_dir ||= (
|
21
|
+
path_parts = ::File.expand_path(__FILE__).split(::File::SEPARATOR)
|
22
|
+
lib_index = path_parts.rindex("lib")
|
23
|
+
path_parts[0...lib_index].join(::File::SEPARATOR) + ::File::SEPARATOR
|
24
|
+
)
|
25
|
+
return @root_dir
|
26
|
+
end
|
27
|
+
|
28
|
+
# returns:: [String] The full expanded path of the +config+ directory
|
29
|
+
# below _root_dir_. All parameters passed in are joined onto the
|
30
|
+
# result. Trailing File::SEPARATOR is guaranteed if _args_ are
|
31
|
+
# *not* present.
|
32
|
+
#
|
33
|
+
def self.config_path(*args)
|
34
|
+
self.sub_path("config", *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
# returns:: [String] The full expanded path of the +data+ directory below
|
38
|
+
# _root_dir_. All parameters passed in are joined onto the
|
39
|
+
# result. Trailing File::SEPARATOR is guaranteed if
|
40
|
+
# _*args_ are *not* present.
|
41
|
+
#
|
42
|
+
def self.data_path(*args)
|
43
|
+
self.sub_path("data", *args)
|
44
|
+
end
|
45
|
+
|
46
|
+
# returns:: [String] The full expanded path of the +lib+ directory below
|
47
|
+
# _root_dir_. All parameters passed in are joined onto the
|
48
|
+
# result. Trailing File::SEPARATOR is guaranteed if
|
49
|
+
# _*args_ are *not* present.
|
50
|
+
#
|
51
|
+
def self.lib_path(*args)
|
52
|
+
self.sub_path("lib", *args)
|
53
|
+
end
|
54
|
+
|
55
|
+
# returns:: [String] The full expanded path of the +ext+ directory below
|
56
|
+
# _root_dir_. All parameters passed in are joined onto the
|
57
|
+
# result. Trailing File::SEPARATOR is guaranteed if
|
58
|
+
# _*args_ are *not* present.
|
59
|
+
#
|
60
|
+
def self.ext_path(*args)
|
61
|
+
self.sub_path("ext", *args)
|
62
|
+
end
|
63
|
+
|
64
|
+
# returns:: [String] The full expanded path of the +spec+ directory below
|
65
|
+
# _root_dir_. All parameters passed in are joined onto the
|
66
|
+
# result. Trailing File::SEPARATOR is guaranteed if
|
67
|
+
# _*args_ are *not* present.
|
68
|
+
#
|
69
|
+
def self.spec_path(*args)
|
70
|
+
self.sub_path("spec", *args)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def self.sub_path(sub,*args)
|
75
|
+
sp = ::File.join(root_dir, sub) + File::SEPARATOR
|
76
|
+
sp = ::File.join(sp, *args) if args
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2023 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module ::Libsql
|
7
|
+
#
|
8
|
+
# A ProfileSampler is a sampler of profile times. It aggregates up profile
|
9
|
+
# events that happen for the same source. It is based upon the RFuzz::Sampler
|
10
|
+
# class from the rfuzz gem
|
11
|
+
#
|
12
|
+
class ProfileSampler
|
13
|
+
#
|
14
|
+
# create a new sampler with the given name
|
15
|
+
#
|
16
|
+
def initialize( name )
|
17
|
+
@name = name
|
18
|
+
reset!
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# reset the internal state so it may be used again
|
23
|
+
#
|
24
|
+
def reset!
|
25
|
+
@sum = 0.0
|
26
|
+
@sumsq = 0.0
|
27
|
+
@n = 0
|
28
|
+
@min = 0.0
|
29
|
+
@max = 0.0
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# add a sample to the calculations
|
34
|
+
#
|
35
|
+
def sample( value )
|
36
|
+
@sum += value
|
37
|
+
@sumsq += (value * value)
|
38
|
+
if @n == 0 then
|
39
|
+
@min = @max = value
|
40
|
+
else
|
41
|
+
@min = value if value < @min
|
42
|
+
@max = value if value > @max
|
43
|
+
end
|
44
|
+
@n += 1
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# return the mean of the data
|
49
|
+
#
|
50
|
+
def mean
|
51
|
+
@sum / @n
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# returns the standard deviation of the data
|
56
|
+
#
|
57
|
+
def stddev
|
58
|
+
begin
|
59
|
+
return 0.0 if ( 1 == @n )
|
60
|
+
Math.sqrt( (@sumsq - ( @sum * @sum / @n)) / (@n-1) )
|
61
|
+
rescue Errno::EDOM
|
62
|
+
return 0.0
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# return all the values as an array
|
68
|
+
#
|
69
|
+
def to_a
|
70
|
+
[ @name, @sum, @sumsq, @n, mean, stddev, @min, @max ]
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# return all the values as a hash
|
75
|
+
#
|
76
|
+
def to_h
|
77
|
+
{ 'name' => @name, 'n' => @n,
|
78
|
+
'sum' => @sum, 'sumsq' => @sumsq, 'mean' => mean,
|
79
|
+
'stddev' => stddev, 'min' => @min, 'max' => @max }
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# return a string containing the sampler summary
|
84
|
+
#
|
85
|
+
def to_s
|
86
|
+
"[%s] => sum: %d, sumsq: %d, n: %d, mean: %0.6f, stddev: %0.6f, min: %d, max: %d" % self.to_a
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# A Profile Tap recives +profile+ events from SQLite which involve the number of
|
93
|
+
# nanoseconds in wall-clock time it took for a particular thing to happen. In
|
94
|
+
# general this +thing+ is an SQL statement.
|
95
|
+
#
|
96
|
+
# It has a well known +profile+ method which when invoked will write the event
|
97
|
+
# to a delegate object.
|
98
|
+
#
|
99
|
+
#
|
100
|
+
class ProfileTap
|
101
|
+
|
102
|
+
attr_reader :samplers
|
103
|
+
|
104
|
+
#
|
105
|
+
# Create a new ProfileTap object that wraps the given object and calls the
|
106
|
+
# method named in +send_to+ ever time a profile event happens.
|
107
|
+
#
|
108
|
+
def initialize( wrapped_obj, send_to = 'profile' )
|
109
|
+
unless wrapped_obj.respond_to?( send_to )
|
110
|
+
raise ::Libsql::Error, "#{wrapped_obj.class.name} does not respond to #{send_to.to_s} "
|
111
|
+
end
|
112
|
+
|
113
|
+
@delegate_obj = wrapped_obj
|
114
|
+
@delegate_method = send_to
|
115
|
+
@samplers = {}
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Record the profile information and send the delegate object the msg and
|
120
|
+
# time information.
|
121
|
+
#
|
122
|
+
def profile( msg, time )
|
123
|
+
msg = msg.gsub(/\s+/,' ')
|
124
|
+
unless sampler = @samplers[msg]
|
125
|
+
sampler = @samplers[msg] = ProfileSampler.new( msg )
|
126
|
+
end
|
127
|
+
sampler.sample( time )
|
128
|
+
@delegate_obj.send( @delegate_method, msg, time )
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ::Libsql
|
2
|
+
##
|
3
|
+
# A base class for use in creating your own progress handler classes
|
4
|
+
#
|
5
|
+
class ProgressHandler
|
6
|
+
def to_proc
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
# the arity of the call method
|
11
|
+
def arity() 0 ; end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Override this method, returning +false+ if the SQLite should act as if
|
15
|
+
# +interrupt!+ had been invoked.
|
16
|
+
#
|
17
|
+
def call
|
18
|
+
raise NotImplementedError, "The progress handler call() method must be implemented"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2023 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
require 'libsql/table'
|
7
|
+
require 'libsql/index'
|
8
|
+
require 'libsql/column'
|
9
|
+
require 'libsql/view'
|
10
|
+
|
11
|
+
module ::Libsql
|
12
|
+
#
|
13
|
+
# An object view of the schema in the SQLite database. If the schema changes
|
14
|
+
# after this class is created, it has no knowledge of that.
|
15
|
+
#
|
16
|
+
class Schema
|
17
|
+
|
18
|
+
# The internal database that this schema is for. Most of the time this will
|
19
|
+
# be 'main' for the main database. For the temp tables, this will be 'temp'
|
20
|
+
# and for any attached databsae, this is the name of attached database.
|
21
|
+
attr_reader :catalog
|
22
|
+
|
23
|
+
# The schema_version at the time this schema was taken.
|
24
|
+
attr_reader :schema_version
|
25
|
+
|
26
|
+
# The Amalagalite::Database this schema is associated with.
|
27
|
+
attr_reader :db
|
28
|
+
|
29
|
+
#
|
30
|
+
# Create a new instance of Schema
|
31
|
+
#
|
32
|
+
def initialize( db, catalog = 'main', master_table = 'sqlite_master' )
|
33
|
+
@db = db
|
34
|
+
@catalog = catalog
|
35
|
+
@schema_version = nil
|
36
|
+
@tables = {}
|
37
|
+
@views = {}
|
38
|
+
@master_table = master_table
|
39
|
+
|
40
|
+
if @master_table == 'sqlite_master' then
|
41
|
+
@temp_schema = ::Libsql::Schema.new( db, 'temp', 'sqlite_temp_master')
|
42
|
+
else
|
43
|
+
@temp_schema = nil
|
44
|
+
end
|
45
|
+
load_schema!
|
46
|
+
end
|
47
|
+
|
48
|
+
def catalog_master_table
|
49
|
+
"#{catalog}.#{@master_table}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def temporary?
|
53
|
+
catalog == "temp"
|
54
|
+
end
|
55
|
+
|
56
|
+
def dirty?()
|
57
|
+
return true if (@schema_version != self.current_version)
|
58
|
+
return false unless @temp_schema
|
59
|
+
return @temp_schema.dirty?
|
60
|
+
end
|
61
|
+
|
62
|
+
def current_version
|
63
|
+
@db.first_value_from("PRAGMA #{catalog}.schema_version")
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# load the schema from the database
|
68
|
+
def load_schema!
|
69
|
+
load_tables
|
70
|
+
load_views
|
71
|
+
if @temp_schema then
|
72
|
+
@temp_schema.load_schema!
|
73
|
+
end
|
74
|
+
@schema_version = self.current_version
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# return the tables, reloading if dirty.
|
80
|
+
# If there is a temp table and a normal table with the same name, then the
|
81
|
+
# temp table is the one that is returned in the hash.
|
82
|
+
def tables
|
83
|
+
load_schema! if dirty?
|
84
|
+
t = @tables
|
85
|
+
if @temp_schema then
|
86
|
+
t = @tables.merge( @temp_schema.tables )
|
87
|
+
end
|
88
|
+
return t
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# load all the tables
|
93
|
+
#
|
94
|
+
def load_tables
|
95
|
+
@tables = {}
|
96
|
+
@db.execute("SELECT tbl_name FROM #{catalog_master_table} WHERE type = 'table' AND name != 'sqlite_sequence'") do |table_info|
|
97
|
+
table = load_table( table_info['tbl_name'] )
|
98
|
+
table.indexes = load_indexes( table )
|
99
|
+
@tables[table.name] = table
|
100
|
+
end
|
101
|
+
return @tables
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Load a single table
|
106
|
+
def load_table( table_name )
|
107
|
+
rows = @db.execute("SELECT tbl_name, sql FROM #{catalog_master_table} WHERE type = 'table' AND tbl_name = ?", table_name)
|
108
|
+
table_info = rows.first
|
109
|
+
table = nil
|
110
|
+
if table_info then
|
111
|
+
table = ::Libsql::Table.new( table_info['tbl_name'], table_info['sql'] )
|
112
|
+
table.schema = self
|
113
|
+
table.columns = load_columns( table )
|
114
|
+
table.indexes = load_indexes( table )
|
115
|
+
@tables[table.name] = table
|
116
|
+
end
|
117
|
+
return table
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# load all the indexes for a particular table
|
122
|
+
#
|
123
|
+
def load_indexes( table )
|
124
|
+
indexes = {}
|
125
|
+
|
126
|
+
@db.prepare("SELECT name, sql FROM #{catalog_master_table} WHERE type ='index' and tbl_name = $name") do |idx_stmt|
|
127
|
+
idx_stmt.execute( "$name" => table.name) do |idx_info|
|
128
|
+
indexes[idx_info['name']] = ::Libsql::Index.new( idx_info['name'], idx_info['sql'], table )
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
@db.execute("PRAGMA index_list( #{@db.quote(table.name)} );") do |idx_list|
|
133
|
+
idx = indexes[idx_list['name']]
|
134
|
+
|
135
|
+
# temporary indexes do not show up in the previous list
|
136
|
+
if idx.nil? then
|
137
|
+
idx = ::Libsql::Index.new( idx_list['name'], nil, table )
|
138
|
+
indexes[idx_list['name']] = idx
|
139
|
+
end
|
140
|
+
|
141
|
+
idx.sequence_number = idx_list['seq']
|
142
|
+
idx.unique = Boolean.to_bool( idx_list['unique'] )
|
143
|
+
|
144
|
+
@db.execute("PRAGMA index_info( #{@db.quote(idx.name)} );") do |col_info|
|
145
|
+
idx.columns << table.columns[col_info['name']]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
return indexes
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# load all the columns for a particular table
|
153
|
+
#
|
154
|
+
def load_columns( table )
|
155
|
+
cols = {}
|
156
|
+
idx = 0
|
157
|
+
@db.execute("PRAGMA #{catalog}.table_info(#{@db.quote(table.name)})") do |row|
|
158
|
+
col = ::Libsql::Column.new( catalog, table.name, row['name'], row['cid'])
|
159
|
+
|
160
|
+
col.default_value = row['dflt_value']
|
161
|
+
|
162
|
+
col.declared_data_type = row['type']
|
163
|
+
col.not_null_constraint = row['notnull']
|
164
|
+
col.primary_key = row['pk']
|
165
|
+
|
166
|
+
# need to remove leading and trailing ' or " from the default value
|
167
|
+
if col.default_value and col.default_value.kind_of?( String ) and ( col.default_value.length >= 2 ) then
|
168
|
+
fc = col.default_value[0].chr
|
169
|
+
lc = col.default_value[-1].chr
|
170
|
+
if fc == lc and ( fc == "'" || fc == '"' ) then
|
171
|
+
col.default_value = col.default_value[1..-2]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
unless table.temporary? then
|
176
|
+
# get more exact information
|
177
|
+
@db.api.table_column_metadata( catalog, table.name, col.name ).each_pair do |key, value|
|
178
|
+
col.send("#{key}=", value)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
col.schema = self
|
182
|
+
cols[col.name] = col
|
183
|
+
idx += 1
|
184
|
+
end
|
185
|
+
return cols
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# return the views, reloading if dirty
|
190
|
+
#
|
191
|
+
# If there is a temp view, and a regular view of the same name, then the
|
192
|
+
# temporary view is the one that is returned in the hash.
|
193
|
+
#
|
194
|
+
def views
|
195
|
+
reload_schema! if dirty?
|
196
|
+
v = @views
|
197
|
+
if @temp_schema then
|
198
|
+
v = @views.merge( @temp_schema.views )
|
199
|
+
end
|
200
|
+
return v
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# load a single view
|
205
|
+
#
|
206
|
+
def load_view( name )
|
207
|
+
rows = @db.execute("SELECT name, sql FROM #{catalog_master_table} WHERE type = 'view' AND name = ?", name )
|
208
|
+
view_info = rows.first
|
209
|
+
view = ::Libsql::View.new( view_info['name'], view_info['sql'] )
|
210
|
+
view.schema = self
|
211
|
+
return view
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# load all the views for the database
|
216
|
+
#
|
217
|
+
def load_views
|
218
|
+
@db.execute("SELECT name, sql FROM #{catalog_master_table} WHERE type = 'view'") do |view_info|
|
219
|
+
view = load_view( view_info['name'] )
|
220
|
+
@views[view.name] = view
|
221
|
+
end
|
222
|
+
return @views
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2023 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
module ::Libsql::SQLite3
|
6
|
+
module Constants
|
7
|
+
module Helpers
|
8
|
+
#
|
9
|
+
# convert an integer value into the string representation of the associated
|
10
|
+
# constant. this is a helper method used by some of the other modules
|
11
|
+
#
|
12
|
+
def name_from_value( value )
|
13
|
+
unless defined? @const_map_from_value
|
14
|
+
@const_map_from_value = {}
|
15
|
+
constants.each do |const_name|
|
16
|
+
c_int = const_get( const_name )
|
17
|
+
@const_map_from_value[c_int] = const_name.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
return @const_map_from_value[ value ]
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# convert a string into the constant value. This is helper method used by
|
25
|
+
# some of the other modules
|
26
|
+
#
|
27
|
+
def value_from_name( name )
|
28
|
+
unless defined? @const_map_from_name
|
29
|
+
@const_map_from_name = {}
|
30
|
+
constants.each do |const_name|
|
31
|
+
c_int = const_get( const_name )
|
32
|
+
@const_map_from_name[ const_name.to_s ] = c_int
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return @const_map_from_name[ name.upcase ]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
##
|
41
|
+
# DataType defines the namespace for all possible SQLite data types.
|
42
|
+
#
|
43
|
+
module DataType
|
44
|
+
extend Helpers
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Open defines the namespace for all possible flags to the Database.open
|
49
|
+
# method
|
50
|
+
#
|
51
|
+
module Open
|
52
|
+
extend Helpers
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Status defines the namespace for all the possible status flags for
|
57
|
+
# ::Libsql::SQLite3::Status objects
|
58
|
+
#
|
59
|
+
module Status
|
60
|
+
extend Helpers
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# DBStatus defines the namespace for all the possible status codes for the
|
65
|
+
# ::Libsql::SQlite3::Database::Status objects.
|
66
|
+
#
|
67
|
+
module DBStatus
|
68
|
+
extend Helpers
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# ResultCode defines the namespace for all possible result codes from an
|
73
|
+
# SQLite API call.
|
74
|
+
#
|
75
|
+
module ResultCode
|
76
|
+
extend Helpers
|
77
|
+
end # end ResultCode
|
78
|
+
|
79
|
+
##
|
80
|
+
# Config defines the namespace for all possible parameter for the
|
81
|
+
# sqlite config API.
|
82
|
+
#
|
83
|
+
module Config
|
84
|
+
extend Helpers
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# StatementStatus defines the namespace for all the possible status codes
|
89
|
+
# for the SQLite prepared statements
|
90
|
+
#
|
91
|
+
module StatementStatus
|
92
|
+
extend Helpers
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|