libsql 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/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 +325 -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
|
+
|
@@ -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
|