turso 0.1.0 → 0.1.2

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.
@@ -1,12 +1,16 @@
1
+ [workspace]
2
+
1
3
  [package]
2
4
  name = "turso_ruby"
3
- version = "0.1.0"
5
+ version = "0.1.2"
4
6
  edition = "2021"
5
7
 
6
8
  [lib]
7
9
  crate-type = ["cdylib"]
10
+ path = "../../../src/lib.rs"
8
11
 
9
12
  [dependencies]
10
13
  magnus = { version = "0.8", features = ["rb-sys"] }
11
- turso_sdk_kit = { path = "../../../../sdk-kit" }
12
- turso_core = { path = "../../../../core" }
14
+ rb-sys = "0.9"
15
+ turso_sdk_kit = { path = "../../../../../sdk-kit", features = ["fts"] }
16
+ turso_core = { path = "../../../../../core", features = ["fts"] }
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "mkmf"
4
- require "rb_sys"
4
+ require "rb_sys/mkmf"
5
5
 
6
- create_rust_makefile("turso_ruby/turso_ruby") do |r|
7
- r.auto_install_rust = true
8
- end
6
+ create_rust_makefile("turso_ruby/turso_ruby")
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Turso
4
+ class Database
5
+ def initialize(path = ":memory:", **options)
6
+ options[:experimental_features] = normalize_experimental_features(options[:experimental_features])
7
+ @native = NativeDatabase.new(path, options)
8
+ @closed = false
9
+ end
10
+
11
+ private
12
+
13
+ def normalize_experimental_features(features)
14
+ return nil if features.nil? || features.empty?
15
+
16
+ Array(features).join(",")
17
+ end
18
+
19
+ public
20
+
21
+ def closed?
22
+ @closed || !@native.open?
23
+ end
24
+
25
+ def close
26
+ return if closed?
27
+ @native.close
28
+ @closed = true
29
+ end
30
+
31
+ def prepare(sql)
32
+ raise Turso::Exception, "database is closed" if closed?
33
+ Statement.new(@native.connection.prepare(sql), @native.connection)
34
+ end
35
+
36
+ def execute(sql, *bind_args)
37
+ stmt = prepare(sql)
38
+ begin
39
+ stmt.bind(*bind_args) unless bind_args.empty?
40
+ stmt.run
41
+ ensure
42
+ stmt.close
43
+ end
44
+ end
45
+
46
+ def execute_batch(sql)
47
+ raise Turso::Exception, "database is closed" if closed?
48
+ @native.connection.execute_batch(sql)
49
+ end
50
+
51
+ def query(sql, *bind_args)
52
+ stmt = prepare(sql)
53
+ stmt.bind(*bind_args) unless bind_args.empty?
54
+ ResultSet.new(stmt)
55
+ end
56
+
57
+ def get_first_row(sql, *bind_args)
58
+ stmt = prepare(sql)
59
+ begin
60
+ stmt.bind(*bind_args) unless bind_args.empty?
61
+ stmt.get
62
+ ensure
63
+ stmt.close
64
+ end
65
+ end
66
+
67
+ def get_first_value(sql, *bind_args)
68
+ row = get_first_row(sql, *bind_args)
69
+ row&.first
70
+ end
71
+
72
+ def transaction(mode = :deferred)
73
+ raise Turso::Exception, "database is closed" if closed?
74
+ execute("BEGIN #{mode.to_s.upcase}")
75
+ begin
76
+ result = yield self
77
+ execute("COMMIT")
78
+ result
79
+ rescue ::Exception
80
+ execute("ROLLBACK")
81
+ raise
82
+ end
83
+ end
84
+
85
+ def busy_timeout=(ms)
86
+ @native.connection.busy_timeout = ms
87
+ end
88
+
89
+ def busy_timeout
90
+ @native.connection.busy_timeout
91
+ end
92
+
93
+ def total_changes
94
+ @native.total_changes
95
+ end
96
+
97
+ def changes
98
+ @native.connection.changes
99
+ end
100
+
101
+ def last_insert_rowid
102
+ @native.last_insert_rowid
103
+ end
104
+
105
+ def interrupt
106
+ @native.connection.interrupt
107
+ end
108
+ end
109
+ end
@@ -8,28 +8,24 @@ module Turso
8
8
  @statement = statement
9
9
  end
10
10
 
11
- def each
12
- return to_enum unless block_given?
11
+ def fields
12
+ @statement.columns.map { |c| c[:name] }
13
+ end
14
+
15
+ def each(&block)
16
+ @statement.each(&block)
17
+ end
18
+
19
+ def next
20
+ @statement.get
21
+ end
13
22
 
14
- columns = column_names
15
- loop do
16
- case @statement.step
17
- when 1 # ROW
18
- yield Row.new(columns, @statement.row)
19
- when 0 # DONE
20
- break
21
- else
22
- raise Turso::Error, "unexpected step status"
23
- end
24
- end
25
- self
26
- ensure
27
- @statement.finalize
23
+ def reset
24
+ @statement.reset
28
25
  end
29
26
 
30
- def column_names
31
- count = @statement.column_count
32
- (0...count).map { |i| @statement.column_name(i) }
27
+ def close
28
+ @statement.close
33
29
  end
34
30
  end
35
31
  end
data/lib/turso/row.rb CHANGED
@@ -2,31 +2,41 @@
2
2
 
3
3
  module Turso
4
4
  class Row
5
- def initialize(columns, values)
6
- @columns = columns
5
+ include Enumerable
6
+
7
+ def initialize(values, column_names)
7
8
  @values = values
9
+ @column_names = column_names
10
+ end
11
+
12
+ def to_a
13
+ @values.dup
8
14
  end
9
15
 
10
16
  def [](key)
11
17
  case key
12
- when Integer then @values[key]
13
- when String, Symbol then @values[@columns.index(key.to_s)]
18
+ when Integer
19
+ @values[key]
20
+ when String, Symbol
21
+ @values[column_index(key.to_s)]
22
+ else
23
+ raise ArgumentError, "invalid key type: #{key.class}"
14
24
  end
15
25
  end
16
26
 
17
- def to_h
18
- @columns.zip(@values).to_h
27
+ def each(&block)
28
+ @values.each(&block)
19
29
  end
20
30
 
21
- def keys = @columns
22
- def values = @values
31
+ def length
32
+ @values.length
33
+ end
34
+ alias size length
23
35
 
24
- def fetch(key, &fallback)
25
- value = self[key]
26
- return value unless value.nil?
27
- return fallback.call(key) if fallback
36
+ private
28
37
 
29
- raise KeyError, "key not found: #{key.inspect}"
38
+ def column_index(name)
39
+ @column_names.index(name) || raise(IndexError, "column #{name} not found")
30
40
  end
31
41
  end
32
42
  end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Turso
4
+ class Statement
5
+ def initialize(native_statement, native_database)
6
+ @native = native_statement
7
+ @database = native_database
8
+ @owner = Thread.current
9
+ end
10
+
11
+ def bind(*args)
12
+ check_owner!
13
+ return self if args.empty?
14
+
15
+ if args.size == 1 && args.first.is_a?(Hash)
16
+ bind_hash(args.first)
17
+ else
18
+ args.each_with_index do |value, index|
19
+ bind_value(index + 1, value)
20
+ end
21
+ end
22
+ self
23
+ end
24
+
25
+ def run(*bind_args)
26
+ check_owner!
27
+ bind(*bind_args) unless bind_args.empty?
28
+ step_loop(consume_rows: false)
29
+ { changes: @database.changes, last_insert_rowid: @database.last_insert_rowid }
30
+ end
31
+
32
+ def get(*bind_args)
33
+ check_owner!
34
+ bind(*bind_args) unless bind_args.empty?
35
+ row = nil
36
+ step_loop(consume_rows: true) { |r| row ||= r; false }
37
+ row
38
+ end
39
+
40
+ def all(*bind_args)
41
+ check_owner!
42
+ bind(*bind_args) unless bind_args.empty?
43
+ rows = []
44
+ step_loop(consume_rows: true) { |r| rows << r; true }
45
+ rows
46
+ end
47
+
48
+ def each(*bind_args)
49
+ return to_enum(:each, *bind_args) unless block_given?
50
+ check_owner!
51
+ bind(*bind_args) unless bind_args.empty?
52
+ step_loop(consume_rows: true) { |r| yield r; true }
53
+ end
54
+
55
+ def columns
56
+ check_owner!
57
+ (1..@native.column_count).map do |i|
58
+ { name: @native.column_name(i - 1), type: @native.column_decltype(i - 1) }
59
+ end
60
+ end
61
+
62
+ def reader?
63
+ @native.column_count > 0
64
+ end
65
+
66
+ def step
67
+ check_owner!
68
+ @native.step
69
+ end
70
+
71
+ def reset
72
+ check_owner!
73
+ @native.reset
74
+ end
75
+
76
+ def close
77
+ @native.finalize
78
+ end
79
+
80
+ private
81
+
82
+ def check_owner!
83
+ unless Thread.current.equal?(@owner)
84
+ raise Turso::Exception, "statement is already in use by another thread"
85
+ end
86
+ end
87
+
88
+ def bind_hash(hash)
89
+ hash.each do |key, value|
90
+ name = key.to_s
91
+ name = ":#{name}" unless name.start_with?(/^[:@?$]/)
92
+ position = @native.named_position(name)
93
+ bind_value(position, value)
94
+ end
95
+ end
96
+
97
+ def bind_value(position, value)
98
+ case value
99
+ when nil
100
+ @native.bind_null(position)
101
+ when Integer
102
+ @native.bind_int(position, value)
103
+ when Float
104
+ @native.bind_double(position, value)
105
+ when String
106
+ if value.encoding == Encoding::ASCII_8BIT
107
+ @native.bind_blob(position, value.b)
108
+ else
109
+ @native.bind_text(position, value)
110
+ end
111
+ when true
112
+ @native.bind_int(position, 1)
113
+ when false
114
+ @native.bind_int(position, 0)
115
+ when Symbol
116
+ @native.bind_text(position, value.to_s)
117
+ when Time
118
+ @native.bind_text(position, value.iso8601)
119
+ else
120
+ @native.bind_text(position, value.to_s)
121
+ end
122
+ end
123
+
124
+ def step_loop(consume_rows:)
125
+ loop do
126
+ result = @native.step
127
+ case result
128
+ when 0 # done
129
+ break
130
+ when 1 # row
131
+ if consume_rows
132
+ row = build_row
133
+ continue = yield row
134
+ break unless continue
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ def build_row
141
+ values = (0...@native.column_count).map { |i| @native.row_value(i) }
142
+ column_names = (0...@native.column_count).map { |i| @native.column_name(i) }
143
+ Row.new(values, column_names)
144
+ end
145
+ end
146
+ end
@@ -1,14 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Turso
4
+ VALID_TRANSACTION_MODES = %i[deferred immediate exclusive concurrent].freeze
5
+
4
6
  class Transaction
5
- def initialize(db)
7
+ def initialize(db, mode = :immediate)
8
+ unless VALID_TRANSACTION_MODES.include?(mode)
9
+ fail ArgumentError, "Invalid transaction mode: #{mode.inspect}. Valid modes: #{VALID_TRANSACTION_MODES.inspect}"
10
+ end
6
11
  @db = db
12
+ @mode = mode
7
13
  @active = false
8
14
  end
9
15
 
10
16
  def begin
11
- @db.execute("BEGIN")
17
+ @db.execute("BEGIN #{@mode.to_s.upcase}")
12
18
  @active = true
13
19
  end
14
20
 
Binary file
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Turso
4
+ VERSION = "0.1.2"
5
+ end
data/lib/turso.rb CHANGED
@@ -2,7 +2,13 @@
2
2
 
3
3
  require "turso/turso_ruby"
4
4
 
5
- require_relative "turso/db"
6
- require_relative "turso/result_set"
7
5
  require_relative "turso/row"
6
+ require_relative "turso/result_set"
7
+ require_relative "turso/statement"
8
+ require_relative "turso/database"
8
9
  require_relative "turso/transaction"
10
+ require_relative "turso/version"
11
+
12
+ module Turso
13
+ Error = Exception
14
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Turso Team
@@ -23,18 +23,37 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0.9'
26
+ - !ruby/object:Gem::Dependency
27
+ name: irb
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
26
40
  executables: []
27
41
  extensions:
28
42
  - ext/turso_ruby/extconf.rb
29
43
  extra_rdoc_files: []
30
44
  files:
45
+ - README.md
46
+ - ext/turso_ruby/Cargo.lock
31
47
  - ext/turso_ruby/Cargo.toml
32
48
  - ext/turso_ruby/extconf.rb
33
49
  - lib/turso.rb
34
- - lib/turso/db.rb
50
+ - lib/turso/database.rb
35
51
  - lib/turso/result_set.rb
36
52
  - lib/turso/row.rb
53
+ - lib/turso/statement.rb
37
54
  - lib/turso/transaction.rb
55
+ - lib/turso/turso_ruby.so
56
+ - lib/turso/version.rb
38
57
  licenses:
39
58
  - MIT
40
59
  metadata: {}
@@ -45,7 +64,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
64
  requirements:
46
65
  - - ">="
47
66
  - !ruby/object:Gem::Version
48
- version: 3.0.0
67
+ version: 3.2.0
49
68
  required_rubygems_version: !ruby/object:Gem::Requirement
50
69
  requirements:
51
70
  - - ">="
data/lib/turso/db.rb DELETED
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Turso
4
- class DB
5
- def initialize(path, **opts)
6
- @database = Database.new(path, opts)
7
- end
8
-
9
- def execute(sql, params = [])
10
- stmt = @database.connection.prepare(sql)
11
- stmt.bind_positional(params) unless params.empty?
12
- case stmt.execute
13
- when Integer
14
- @database.last_insert_rowid
15
- when 0
16
- @database.changes
17
- end
18
- ensure
19
- stmt&.finalize
20
- end
21
-
22
- def query(sql, params = [])
23
- stmt = @database.connection.prepare(sql)
24
- stmt.bind_positional(params) unless params.empty?
25
- ResultSet.new(stmt)
26
- end
27
-
28
- def transaction(&block)
29
- if block
30
- execute("BEGIN")
31
- begin
32
- result = block.call(self)
33
- execute("COMMIT")
34
- result
35
- rescue StandardError
36
- execute("ROLLBACK")
37
- raise
38
- end
39
- else
40
- Transaction.new(self)
41
- end
42
- end
43
-
44
- def close
45
- @database.close
46
- end
47
-
48
- def closed?
49
- !@database.open?
50
- end
51
- end
52
- end