libsql 0.1.0-x64-mingw-ucrt
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/3.1/libsql_ext.so +0 -0
- data/lib/libsql/3.2/libsql_ext.so +0 -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 +329 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
module ::Libsql::SQLite3
|
2
|
+
class Database
|
3
|
+
##
|
4
|
+
# A wrapper around a proc for use as an SQLite Ddatabase fuction
|
5
|
+
#
|
6
|
+
# f = Function.new( 'md5', lambda { |x| Digest::MD5.hexdigest( x.to_s ) } )
|
7
|
+
#
|
8
|
+
class Function
|
9
|
+
|
10
|
+
# the name of the function, and how it will be called in SQL
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# The unique signature of this function. This is used to determin if the
|
14
|
+
# function is already registered or not
|
15
|
+
#
|
16
|
+
def self.signature( name, arity )
|
17
|
+
"#{name}/#{arity}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Initialize with the name and the Proc
|
21
|
+
#
|
22
|
+
def initialize( name, _proc )
|
23
|
+
@name = name
|
24
|
+
@function = _proc
|
25
|
+
end
|
26
|
+
|
27
|
+
# The unique signature of this function
|
28
|
+
#
|
29
|
+
def signature
|
30
|
+
@signature ||= Function.signature( name, arity )
|
31
|
+
end
|
32
|
+
alias :to_s :signature
|
33
|
+
|
34
|
+
# The arity of SQL function, -1 means it is takes a variable number of
|
35
|
+
# arguments.
|
36
|
+
#
|
37
|
+
def arity
|
38
|
+
@function.arity
|
39
|
+
end
|
40
|
+
|
41
|
+
# Invoke the proc
|
42
|
+
#
|
43
|
+
def call( *args )
|
44
|
+
@function.call( *args )
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'libsql/sqlite3/constants'
|
2
|
+
module ::Libsql::SQLite3
|
3
|
+
class Database
|
4
|
+
#
|
5
|
+
# A Stat represents a single Database Status code and its current highwater mark.
|
6
|
+
#
|
7
|
+
# Some stats may not have a current or a highwater value, in those cases
|
8
|
+
# the associated _has_current?_ or _has_highwater?_ method returns false and the
|
9
|
+
# _current_ or _highwater_ method also returns +nil+.
|
10
|
+
#
|
11
|
+
class Stat
|
12
|
+
attr_reader :name
|
13
|
+
attr_reader :code
|
14
|
+
|
15
|
+
def initialize( api_db, name )
|
16
|
+
@name = name
|
17
|
+
@code = ::Libsql::SQLite3::Constants::DBStatus.value_from_name( name )
|
18
|
+
@current = nil
|
19
|
+
@highwater = nil
|
20
|
+
@api_db = api_db
|
21
|
+
end
|
22
|
+
|
23
|
+
def current
|
24
|
+
update!
|
25
|
+
return @current
|
26
|
+
end
|
27
|
+
|
28
|
+
def highwater
|
29
|
+
update!
|
30
|
+
return @highwater
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# reset the given stat's highwater mark. This will also populate the
|
35
|
+
# _@current_ and _@highwater_ instance variables
|
36
|
+
#
|
37
|
+
def reset!
|
38
|
+
update!( true )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Top level Status object holding all the Stat objects indicating the DBStatus
|
44
|
+
# of the SQLite3 C library.
|
45
|
+
#
|
46
|
+
class DBStatus
|
47
|
+
::Libsql::SQLite3::Constants::DBStatus.constants.each do |const_name|
|
48
|
+
method_name = const_name.downcase
|
49
|
+
module_eval( <<-code, __FILE__, __LINE__ )
|
50
|
+
def #{method_name}
|
51
|
+
@#{method_name} ||= ::Libsql::SQLite3::Database::Stat.new( self.api_db, '#{method_name}' )
|
52
|
+
end
|
53
|
+
code
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :api_db
|
57
|
+
|
58
|
+
def initialize( api_db )
|
59
|
+
@api_db = api_db
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# return the DBstatus object for the sqlite database
|
64
|
+
def status
|
65
|
+
@status ||= DBStatus.new( self )
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2023 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
module ::Libsql
|
6
|
+
module SQLite3
|
7
|
+
module LibsqlVersion
|
8
|
+
def self.compiled_matches_runtime?
|
9
|
+
self.compiled_version == self.runtime_version
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Version of libsql that ships with
|
14
|
+
LIBSQL_VERSION = LibsqlVersion.to_s.freeze
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
unless ::Libsql::SQLite3::LibsqlVersion.compiled_matches_runtime? then
|
19
|
+
warn <<eom
|
20
|
+
You are seeing something odd. The compiled version of libsql that
|
21
|
+
is embedded in this extension is for some reason, not being used.
|
22
|
+
The version in the extension is #{::Libsql::SQLite3::LibsqlVersion.compiled_version} and the version that
|
23
|
+
as been loaded as a shared library is #{::Libsql::SQLite3::LibsqlVersion.runtime_version}. These versions
|
24
|
+
should be the same, but they are not.
|
25
|
+
|
26
|
+
One known issue is if you are using this libary in conjunction with
|
27
|
+
Hitimes on Mac OS X. You should make sure that "require 'libsql'"
|
28
|
+
appears before "require 'hitimes'" in your ruby code.
|
29
|
+
|
30
|
+
This is a non-trivial problem, and I am working on it.
|
31
|
+
eom
|
32
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'libsql/sqlite3/constants'
|
2
|
+
module ::Libsql::SQLite3
|
3
|
+
|
4
|
+
#
|
5
|
+
# A Stat represents a single Status code and its current highwater mark.
|
6
|
+
#
|
7
|
+
# Some stats may not have a current or a highwater value, in those cases
|
8
|
+
# the associated _has_current?_ or _has_highwater?_ method returns false and the
|
9
|
+
# _current_ or _highwater_ method also returns +nil+.
|
10
|
+
#
|
11
|
+
class Stat
|
12
|
+
attr_reader :name
|
13
|
+
attr_reader :code
|
14
|
+
|
15
|
+
def initialize( name )
|
16
|
+
@name = name
|
17
|
+
@code = ::Libsql::SQLite3::Constants::Status.value_from_name( name )
|
18
|
+
@current = nil
|
19
|
+
@highwater = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def current
|
23
|
+
update!
|
24
|
+
return @current
|
25
|
+
end
|
26
|
+
|
27
|
+
def highwater
|
28
|
+
update!
|
29
|
+
return @highwater
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# reset the given stat's highwater mark. This will also populate the
|
34
|
+
# _@current_ and _@highwater_ instance variables
|
35
|
+
#
|
36
|
+
def reset!
|
37
|
+
update!( true )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Top level Status object holding all the Stat objects indicating the Status
|
43
|
+
# of the SQLite3 C library.
|
44
|
+
#
|
45
|
+
class Status
|
46
|
+
::Libsql::SQLite3::Constants::Status.constants.each do |const_name|
|
47
|
+
method_name = const_name.downcase
|
48
|
+
module_eval( <<-code, __FILE__, __LINE__ )
|
49
|
+
def #{method_name}
|
50
|
+
@#{method_name} ||= ::Libsql::SQLite3::Stat.new( '#{method_name}' )
|
51
|
+
end
|
52
|
+
code
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# return the status object for the sqlite database
|
57
|
+
def self.status
|
58
|
+
@status ||= Status.new
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2023 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
module ::Libsql
|
6
|
+
module SQLite3
|
7
|
+
module Version
|
8
|
+
# Sqlite3 version number is equal to
|
9
|
+
# MAJOR * 1_000_000 + MINOR * 1_000 + RELEASE
|
10
|
+
|
11
|
+
# major version number of the SQLite C library
|
12
|
+
MAJOR = (to_i / 1_000_000).freeze
|
13
|
+
|
14
|
+
# minor version number of the SQLite C library
|
15
|
+
MINOR = ((to_i % 1_000_000) / 1_000).freeze
|
16
|
+
|
17
|
+
# release version number of the SQLite C library
|
18
|
+
RELEASE = (to_i % 1_000).freeze
|
19
|
+
|
20
|
+
#
|
21
|
+
# call-seq:
|
22
|
+
# ::Libsql::SQLite3::Version.to_a -> [ MAJOR, MINOR, RELEASE ]
|
23
|
+
#
|
24
|
+
# Return the SQLite C library version number as an array of MAJOR, MINOR,
|
25
|
+
# RELEASE
|
26
|
+
#
|
27
|
+
def self.to_a
|
28
|
+
[ MAJOR, MINOR, RELEASE ]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.compiled_matches_runtime?
|
32
|
+
self.compiled_version == self.runtime_version
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Version of SQLite that ships with ::Libsql
|
37
|
+
VERSION = Version.to_s.freeze
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
unless ::Libsql::SQLite3::Version.compiled_matches_runtime? then
|
42
|
+
warn <<eom
|
43
|
+
You are seeing something odd. The compiled version of SQLite that
|
44
|
+
is embedded in this extension is for some reason, not being used.
|
45
|
+
The version in the extension is #{::Libsql::SQLite3::Version.compiled_version} and the version that
|
46
|
+
as been loaded as a shared library is #{::Libsql::SQLite3::Version.runtime_version}. These versions
|
47
|
+
should be the same, but they are not.
|
48
|
+
|
49
|
+
One known issue is if you are using this libary in conjunction with
|
50
|
+
Hitimes on Mac OS X. You should make sure that "require 'libsql'"
|
51
|
+
appears before "require 'hitimes'" in your ruby code.
|
52
|
+
|
53
|
+
This is a non-trivial problem, and I am working on it.
|
54
|
+
eom
|
55
|
+
end
|
@@ -0,0 +1,421 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2023 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
#
|
6
|
+
require 'date'
|
7
|
+
require 'arrayfields'
|
8
|
+
require 'ostruct'
|
9
|
+
|
10
|
+
module ::Libsql
|
11
|
+
class Statement
|
12
|
+
|
13
|
+
include ::Libsql::SQLite3::Constants
|
14
|
+
|
15
|
+
attr_reader :db
|
16
|
+
attr_reader :api
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# special column names that indicate that indicate the column is a rowid
|
20
|
+
def rowid_column_names
|
21
|
+
@rowid_column_names ||= %w[ ROWID OID _ROWID_ ]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Initialize a new statement on the database.
|
27
|
+
#
|
28
|
+
def initialize( db, sql )
|
29
|
+
@db = db
|
30
|
+
#prepare_method = @db.utf16? ? :prepare16 : :prepare
|
31
|
+
prepare_method = :prepare
|
32
|
+
@param_positions = {}
|
33
|
+
@stmt_api = @db.api.send( prepare_method, sql )
|
34
|
+
@blobs_to_write = []
|
35
|
+
@rowid_index = nil
|
36
|
+
@result_meta = nil
|
37
|
+
@open = true
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# is the statement open for business
|
42
|
+
#
|
43
|
+
def open?
|
44
|
+
@open
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Is the special column "ROWID", "OID", or "_ROWID_" used?
|
49
|
+
#
|
50
|
+
def using_rowid_column?
|
51
|
+
not @rowid_index.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# reset the Statement back to it state right after the constructor returned,
|
56
|
+
# except if any variables have been bound to parameters, those are still
|
57
|
+
# bound.
|
58
|
+
#
|
59
|
+
def reset!
|
60
|
+
@stmt_api.reset!
|
61
|
+
@param_positions = {}
|
62
|
+
@blobs_to_write.clear
|
63
|
+
@rowid_index = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# reset the Statement back to it state right after the constructor returned,
|
68
|
+
# AND clear all parameter bindings.
|
69
|
+
#
|
70
|
+
def reset_and_clear_bindings!
|
71
|
+
reset!
|
72
|
+
@stmt_api.clear_bindings!
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# reset the statment in preparation for executing it again
|
77
|
+
#
|
78
|
+
def reset_for_next_execute!
|
79
|
+
@stmt_api.reset!
|
80
|
+
@stmt_api.clear_bindings!
|
81
|
+
@blobs_to_write.clear
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Execute the statement with the given parameters
|
86
|
+
#
|
87
|
+
# If a block is given, then yield each returned row to the block. If no
|
88
|
+
# block is given then return all rows from the result. No matter what the
|
89
|
+
# prepared statement should be reset before returning the final time.
|
90
|
+
#
|
91
|
+
def execute( *params )
|
92
|
+
bind( *params )
|
93
|
+
begin
|
94
|
+
# save the error state at the beginning of the execution. We only want to
|
95
|
+
# reraise the error if it was raised during this execution.
|
96
|
+
s_before = $!
|
97
|
+
if block_given? then
|
98
|
+
while row = next_row
|
99
|
+
yield row
|
100
|
+
end
|
101
|
+
else
|
102
|
+
all_rows
|
103
|
+
end
|
104
|
+
ensure
|
105
|
+
s = $!
|
106
|
+
begin
|
107
|
+
reset_for_next_execute!
|
108
|
+
rescue
|
109
|
+
# rescuing nothing on purpose
|
110
|
+
end
|
111
|
+
raise s if s != s_before
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Bind parameters to the sql statement.
|
117
|
+
#
|
118
|
+
# Bindings in SQLite can have a number of formats:
|
119
|
+
#
|
120
|
+
# ?
|
121
|
+
# ?num
|
122
|
+
# :var
|
123
|
+
# @var
|
124
|
+
# $var
|
125
|
+
#
|
126
|
+
# Where 'num' is an Integer and 'var'is an alphanumerical variable.
|
127
|
+
# They may exist in the SQL for which this Statement was created.
|
128
|
+
#
|
129
|
+
# ::Libsql binds parameters to these variables in the following manner:
|
130
|
+
#
|
131
|
+
# If bind is passed in an Array, either as +bind( "foo", "bar", "baz")+ or
|
132
|
+
# as bind( ["foo", "bar", "baz"] ) then each of the params is assumed to be
|
133
|
+
# positionally bound to the statement( ?, ?num ).
|
134
|
+
#
|
135
|
+
# If bind is passed a Hash, either as +bind( :foo => 1, :bar => 'sqlite' )+
|
136
|
+
# or as bind( { :foo => 1, 'bar' => 'sqlite' }) then it is assumed that each
|
137
|
+
# parameter should be bound as a named parameter (:var, @var, $var).
|
138
|
+
#
|
139
|
+
# If bind is not passed any parameters, or nil, then nothing happens.
|
140
|
+
#
|
141
|
+
def bind( *params )
|
142
|
+
if params.nil? or params.empty? then
|
143
|
+
check_parameter_count!( 0 )
|
144
|
+
return nil
|
145
|
+
end
|
146
|
+
|
147
|
+
if params.first.instance_of?( Hash ) then
|
148
|
+
bind_named_parameters( params.first )
|
149
|
+
elsif params.first.instance_of?( Array ) then
|
150
|
+
bind_positional_parameters( *params )
|
151
|
+
else
|
152
|
+
bind_positional_parameters( params )
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Bind parameters to the statement based upon named parameters
|
158
|
+
#
|
159
|
+
def bind_named_parameters( params )
|
160
|
+
check_parameter_count!( params.size )
|
161
|
+
params.each_pair do | param, value |
|
162
|
+
position = param_position_of( param )
|
163
|
+
if position > 0 then
|
164
|
+
bind_parameter_to( position, value )
|
165
|
+
else
|
166
|
+
raise ::Libsql::Error, "Unable to find parameter '#{param}' in SQL statement [#{sql}]"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Bind parameters to the statements based upon positions.
|
173
|
+
#
|
174
|
+
def bind_positional_parameters( params )
|
175
|
+
check_parameter_count!( params.size )
|
176
|
+
params.each_with_index do |value, index|
|
177
|
+
position = index + 1
|
178
|
+
bind_parameter_to( position, value )
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# bind a single parameter to a particular position
|
184
|
+
#
|
185
|
+
def bind_parameter_to( position, value )
|
186
|
+
bind_type = db.type_map.bind_type_of( value )
|
187
|
+
case bind_type
|
188
|
+
when DataType::FLOAT
|
189
|
+
@stmt_api.bind_double( position, value )
|
190
|
+
when DataType::INTEGER
|
191
|
+
@stmt_api.bind_int64( position, value )
|
192
|
+
when DataType::NULL
|
193
|
+
@stmt_api.bind_null( position )
|
194
|
+
when DataType::TEXT
|
195
|
+
@stmt_api.bind_text( position, value.to_s )
|
196
|
+
when DataType::BLOB
|
197
|
+
if value.incremental? then
|
198
|
+
@stmt_api.bind_zeroblob( position, value.length )
|
199
|
+
@blobs_to_write << value
|
200
|
+
else
|
201
|
+
@stmt_api.bind_blob( position, value.source )
|
202
|
+
end
|
203
|
+
else
|
204
|
+
raise ::Libsql::Error, "Unknown binding type of #{bind_type} from #{db.type_map.class.name}.bind_type_of"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
##
|
210
|
+
# Find and cache the binding parameter indexes
|
211
|
+
#
|
212
|
+
def param_position_of( name )
|
213
|
+
ns = name.to_s
|
214
|
+
unless pos = @param_positions[ns]
|
215
|
+
pos = @param_positions[ns] = @stmt_api.parameter_index( ns )
|
216
|
+
end
|
217
|
+
return pos
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Check and make sure that the number of parameters aligns with the number
|
222
|
+
# that sqlite expects
|
223
|
+
#
|
224
|
+
def check_parameter_count!( num )
|
225
|
+
expected = @stmt_api.parameter_count
|
226
|
+
if num != expected then
|
227
|
+
raise ::Libsql::Error, "#{sql} has #{expected} parameters, but #{num} were passed to bind."
|
228
|
+
end
|
229
|
+
return expected
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Write any blobs that have been bound to parameters to the database. This
|
234
|
+
# assumes that the blobs go into the last inserted row
|
235
|
+
#
|
236
|
+
def write_blobs
|
237
|
+
unless @blobs_to_write.empty?
|
238
|
+
@blobs_to_write.each do |blob|
|
239
|
+
blob.write_to_column!
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Iterate over the results of the statement returning each row of results
|
246
|
+
# as a hash by +column_name+. The column names are the value after an
|
247
|
+
# 'AS' in the query or default chosen by sqlite.
|
248
|
+
#
|
249
|
+
def each
|
250
|
+
while row = next_row
|
251
|
+
yield row
|
252
|
+
end
|
253
|
+
return self
|
254
|
+
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# Return the next row of data, with type conversion as indicated by the
|
258
|
+
# Database#type_map
|
259
|
+
#
|
260
|
+
def next_row
|
261
|
+
row = []
|
262
|
+
case rc = @stmt_api.step
|
263
|
+
when ResultCode::ROW
|
264
|
+
result_meta.each_with_index do |col, idx|
|
265
|
+
value = nil
|
266
|
+
column_type = @stmt_api.column_type( idx )
|
267
|
+
case column_type
|
268
|
+
when DataType::TEXT
|
269
|
+
value = @stmt_api.column_text( idx )
|
270
|
+
when DataType::FLOAT
|
271
|
+
value = @stmt_api.column_double( idx )
|
272
|
+
when DataType::INTEGER
|
273
|
+
value = @stmt_api.column_int64( idx )
|
274
|
+
when DataType::NULL
|
275
|
+
value = nil
|
276
|
+
when DataType::BLOB
|
277
|
+
# if the rowid column is encountered, then we can use an incremental
|
278
|
+
# blob api, otherwise we have to use the all at once version.
|
279
|
+
if using_rowid_column? then
|
280
|
+
value = ::Libsql::Blob.new( :db_blob => SQLite3::Blob.new( db.api,
|
281
|
+
col.schema.db,
|
282
|
+
col.schema.table,
|
283
|
+
col.schema.name,
|
284
|
+
@stmt_api.column_int64( @rowid_index ),
|
285
|
+
"r"),
|
286
|
+
:column => col.schema)
|
287
|
+
else
|
288
|
+
value = ::Libsql::Blob.new( :string => @stmt_api.column_blob( idx ), :column => col.schema )
|
289
|
+
end
|
290
|
+
else
|
291
|
+
raise ::Libsql::Error, "BUG! : Unknown SQLite column type of #{column_type}"
|
292
|
+
end
|
293
|
+
|
294
|
+
row << db.type_map.result_value_of( col.schema.declared_data_type, value )
|
295
|
+
end
|
296
|
+
row.fields = result_fields
|
297
|
+
when ResultCode::DONE
|
298
|
+
row = nil
|
299
|
+
write_blobs
|
300
|
+
else
|
301
|
+
self.close # must close so that the error message is guaranteed to be pushed into the database handler
|
302
|
+
# and we can can call last_error_message on it
|
303
|
+
msg = "SQLITE ERROR #{rc} (#{::Libsql::SQLite3::Constants::ResultCode.name_from_value( rc )}) : #{@db.api.last_error_message}"
|
304
|
+
raise ::Libsql::SQLite3::Error, msg
|
305
|
+
end
|
306
|
+
return row
|
307
|
+
end
|
308
|
+
|
309
|
+
##
|
310
|
+
# Return all rows from the statement as one array
|
311
|
+
#
|
312
|
+
def all_rows
|
313
|
+
rows = []
|
314
|
+
while row = next_row
|
315
|
+
rows << row
|
316
|
+
end
|
317
|
+
return rows
|
318
|
+
end
|
319
|
+
|
320
|
+
##
|
321
|
+
# Inspect the statement and gather all the meta information about the
|
322
|
+
# results, include the name of the column result column and the origin
|
323
|
+
# column. The origin column is the original database.table.column the value
|
324
|
+
# comes from.
|
325
|
+
#
|
326
|
+
# The full meta information from the origin column is also obtained for help
|
327
|
+
# in doing type conversion.
|
328
|
+
#
|
329
|
+
# As iteration over the row meta informatio happens, record if the special
|
330
|
+
# "ROWID", "OID", or "_ROWID_" column is encountered. If that column is
|
331
|
+
# encountered then we make note of it.
|
332
|
+
#
|
333
|
+
def result_meta
|
334
|
+
unless @result_meta
|
335
|
+
meta = []
|
336
|
+
column_count.times do |idx|
|
337
|
+
column_meta = ::OpenStruct.new
|
338
|
+
column_meta.name = @stmt_api.column_name( idx )
|
339
|
+
|
340
|
+
db_name = @stmt_api.column_database_name( idx )
|
341
|
+
tbl_name = @stmt_api.column_table_name( idx )
|
342
|
+
col_name = @stmt_api.column_origin_name( idx )
|
343
|
+
|
344
|
+
column_meta.schema = ::Libsql::Column.new( db_name, tbl_name, col_name, idx )
|
345
|
+
column_meta.schema.declared_data_type = @stmt_api.column_declared_type( idx )
|
346
|
+
|
347
|
+
# only check for rowid if we have a table name and it is not one of the
|
348
|
+
# sqlite_master tables. We could get recursion in those cases.
|
349
|
+
if not using_rowid_column? and tbl_name and
|
350
|
+
not %w[ sqlite_master sqlite_temp_master ].include?( tbl_name ) and is_column_rowid?( tbl_name, col_name ) then
|
351
|
+
@rowid_index = idx
|
352
|
+
end
|
353
|
+
|
354
|
+
meta << column_meta
|
355
|
+
end
|
356
|
+
|
357
|
+
@result_meta = meta
|
358
|
+
end
|
359
|
+
return @result_meta
|
360
|
+
end
|
361
|
+
|
362
|
+
##
|
363
|
+
# is the column indicated by the Column a 'rowid' column
|
364
|
+
#
|
365
|
+
def is_column_rowid?( table_name, column_name )
|
366
|
+
table_schema = @db.schema.tables[table_name]
|
367
|
+
return false unless table_schema
|
368
|
+
|
369
|
+
column_schema = table_schema.columns[column_name]
|
370
|
+
if column_schema then
|
371
|
+
if column_schema.primary_key? and column_schema.declared_data_type and column_schema.declared_data_type.upcase == "INTEGER" then
|
372
|
+
return true
|
373
|
+
end
|
374
|
+
else
|
375
|
+
return true if Statement.rowid_column_names.include?( column_name.upcase )
|
376
|
+
end
|
377
|
+
return false
|
378
|
+
end
|
379
|
+
|
380
|
+
##
|
381
|
+
# Return the array of field names for the result set, the field names are
|
382
|
+
# all strings
|
383
|
+
#
|
384
|
+
def result_fields
|
385
|
+
@fields ||= result_meta.collect { |m| m.name }
|
386
|
+
end
|
387
|
+
|
388
|
+
##
|
389
|
+
# Return any unsued SQL from the statement
|
390
|
+
#
|
391
|
+
def remaining_sql
|
392
|
+
@stmt_api.remaining_sql
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
##
|
397
|
+
# return the number of columns in the result of this query
|
398
|
+
#
|
399
|
+
def column_count
|
400
|
+
@stmt_api.column_count
|
401
|
+
end
|
402
|
+
|
403
|
+
##
|
404
|
+
# return the raw sql that was originally used to prepare the statement
|
405
|
+
#
|
406
|
+
def sql
|
407
|
+
@stmt_api.sql
|
408
|
+
end
|
409
|
+
|
410
|
+
##
|
411
|
+
# Close the statement. The statement is no longer valid for use after it
|
412
|
+
# has been closed.
|
413
|
+
#
|
414
|
+
def close
|
415
|
+
if open? then
|
416
|
+
@stmt_api.close
|
417
|
+
@open = false
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|