libsql-rb 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 20fc3deb5389d0d74ca8af4ad378a4c138846c0cc0e0dcff10967214de97d0f2
4
+ data.tar.gz: fbe8d990103294b4d699f20aac94f95ed6175a3470aa19e7f4d4cc09142bac75
5
+ SHA512:
6
+ metadata.gz: 1781b3d7c2aa0ea1192305833f269c80758eda4d079ddb5d41158679ed131c20451561923588e5b77b37c6f24d0e52a23d106a06aa054c97c0e035ecdfa1736c
7
+ data.tar.gz: 7b03dceb06e39b77f80e90aa7eb83b3e34bf5553511021a415b54f94126c133f679a691e3313a520da633593335fbf1c9c872e7e583396c076514a5ba918360f
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,47 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Style/FrozenStringLiteralComment:
13
+ Enabled: true
14
+ EnforcedStyle: never
15
+
16
+ Layout/LineLength:
17
+ Max: 120
18
+
19
+ Style/Documentation:
20
+ Enabled: false
21
+
22
+ Layout/FirstArrayElementIndentation:
23
+ Enabled: true
24
+ EnforcedStyle: consistent
25
+
26
+ Layout/FirstHashElementIndentation:
27
+ Enabled: true
28
+ EnforcedStyle: consistent
29
+
30
+ Metrics:
31
+ Enabled: false
32
+
33
+ Layout/IndentationConsistency:
34
+ Enabled: true
35
+ EnforcedStyle: indented_internal_methods
36
+
37
+ Style/UnlessElse:
38
+ Enabled: false
39
+
40
+ Style/ConditionalAssignment:
41
+ Enabled: false
42
+
43
+ Layout/CaseIndentation:
44
+ EnforcedStyle: end
45
+
46
+ Layout/EndAlignment:
47
+ EnforcedStyleAlignWith: variable
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Leon Cruz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Libsql-rb
2
+
3
+ A simple wrapper around the Libsql HTTP protocol. Does not support the Libsql protocol,
4
+ only the HTTP method based on the [documentation](https://github.com/tursodatabase/libsql/blob/main/docs/HRANA_2_SPEC.md)
5
+
6
+ ## Installation
7
+
8
+ $ gem install libsql-rb
9
+
10
+ ## Usage
11
+
12
+ #### Example
13
+
14
+ ```ruby
15
+ client = Libsql::Client.new host: "http://localhost:8080"
16
+
17
+ client.execute "create table users(name, email, password)"
18
+
19
+ # Is possible to pass positional arguments
20
+ client.execute "insert into users(name, email, password) values(?, ?, ?)", ["User 1", "user1@email..com", "1111111"]
21
+
22
+ # or named aguments
23
+ client.execute "insert into users(name, email, password) values(:name, :email, :password)", ["User 1", "user1@email..com", "1111111"]
24
+
25
+ result = client.execute "select * from users"
26
+
27
+ result.rows # get the rows as Struct
28
+ ```
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ require "rubocop/rake_task"
7
+
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec rubocop]
@@ -0,0 +1,3 @@
1
+ module Libsql
2
+ VERSION = "0.0.1".freeze
3
+ end
data/lib/libsql-rb.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative "./libsql" # rubocop:disable Naming/FileName
data/lib/libsql.rb ADDED
@@ -0,0 +1,224 @@
1
+ require "json"
2
+ require "net/http"
3
+
4
+ module Libsql
5
+ class Client
6
+ STMT_ENDPOINT = "/v2/pipeline".freeze
7
+
8
+ def initialize(params = {})
9
+ @url = params[:url]
10
+ @host = params[:host]
11
+ @port = params[:port]
12
+
13
+ _build_uri
14
+ end
15
+
16
+ def execute(query_string, args = nil)
17
+ argument_builder = case args
18
+ in Array
19
+ PositionalArgBuilder.new(*args)
20
+ in Hash
21
+ NamedArgBuilder.new(**args)
22
+ in NilClass
23
+ EmptyBuilder.new
24
+ else
25
+ raise NotImplementedError
26
+ end
27
+
28
+ if argument_builder.empty?
29
+ body = {
30
+ requests: [
31
+ { type: :execute, stmt: { sql: query_string } },
32
+ { type: :close }
33
+ ]
34
+ }
35
+ else
36
+ body = {
37
+ requests: [
38
+ { type: :execute, stmt: { sql: query_string, argument_builder.key => argument_builder.build } },
39
+ { type: :close }
40
+ ]
41
+ }
42
+ end
43
+
44
+ response = http_client.post(STMT_ENDPOINT, body)
45
+
46
+ raise StandardError, response.results.first["error"]["message"] if response.failure?
47
+
48
+ QueryResult.new(response)
49
+ end
50
+
51
+ private
52
+
53
+ def http_client
54
+ @http_client ||= HTTPClient.new(@uri)
55
+ end
56
+
57
+ def _build_uri
58
+ unless @url
59
+ @uri = URI("#{@host}:#{@port}")
60
+ else
61
+ @uri = URI(@url)
62
+ end
63
+ end
64
+ end
65
+
66
+ class EmptyBuilder
67
+ def empty? = true
68
+
69
+ def key = :no_key
70
+ end
71
+
72
+ class PositionalArgBuilder
73
+ attr_reader :args
74
+
75
+ def initialize(*args)
76
+ @args = args
77
+ end
78
+
79
+ def empty? = @args.empty?
80
+
81
+ def key = :args
82
+
83
+ def build
84
+ args.reduce([]) do |acc, value|
85
+ type = case value
86
+ in Integer
87
+ "integer"
88
+ in NilClass
89
+ "null"
90
+ else
91
+ "text"
92
+ end
93
+
94
+ acc << { type:, value: }
95
+ end
96
+ end
97
+ end
98
+
99
+ class NamedArgBuilder
100
+ attr_reader :kwargs
101
+
102
+ def initialize(**kwargs)
103
+ @kwargs = kwargs
104
+ end
105
+
106
+ def empty? = @kwargs.empty?
107
+
108
+ def key = :named_args
109
+
110
+ def build
111
+ kwargs.reduce([]) do |acc, (key, value)|
112
+ type = case value
113
+ in Integer
114
+ "integer"
115
+ in NilClass
116
+ "null"
117
+ else
118
+ "text"
119
+ end
120
+
121
+ acc << { name: key, value: { type:, value: } }
122
+ end
123
+ end
124
+ end
125
+
126
+ class QueryResult
127
+ def initialize(response)
128
+ @results = response.results
129
+ end
130
+
131
+ def rows
132
+ @results.first["response"]["result"]["rows"].map do |register|
133
+ row = Row.new
134
+
135
+ register.map.with_index do |r, index|
136
+ row.set(cols[index]["name"], r["value"])
137
+ end
138
+
139
+ row
140
+ end.flatten
141
+ end
142
+
143
+ private
144
+
145
+ def cols
146
+ @results.first["response"]["result"]["cols"]
147
+ end
148
+
149
+ class Row
150
+ def set(column, value)
151
+ instance_variable_set :"@#{column}", value
152
+
153
+ self.class.class_eval do
154
+ define_method column.to_sym, -> { value }
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ class HTTPClient
161
+ def initialize(base_uri)
162
+ @base_uri = base_uri
163
+ end
164
+
165
+ def get(endpoint)
166
+ uri = base_uri.merge endpoint
167
+
168
+ request = Net::HTTP::Get.new(uri)
169
+
170
+ execute(uri, request)
171
+ end
172
+
173
+ def post(endpoint, body = {})
174
+ uri = base_uri.merge endpoint
175
+
176
+ request = Net::HTTP::Post.new(uri)
177
+ request["Content-Type"] = "application/json"
178
+
179
+ request.body = JSON.generate(body)
180
+
181
+ execute(uri, request)
182
+ end
183
+
184
+ private
185
+
186
+ attr_accessor :base_uri
187
+
188
+ def execute(uri, request)
189
+ use_ssl = uri.scheme == "https"
190
+ request["Content-Type"] = "application/json"
191
+
192
+ resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl:) do |http|
193
+ http.request(request)
194
+ end
195
+
196
+ Response.new(resp)
197
+ end
198
+
199
+ class Response
200
+ attr_reader :status_code
201
+
202
+ def initialize(response)
203
+ @json_response = response.body
204
+ @status_code = response.code
205
+ end
206
+
207
+ def body
208
+ @body ||= JSON.parse(@json_response)
209
+ end
210
+
211
+ def success?
212
+ body["results"].all? { |r| r["type"] == "ok" }
213
+ end
214
+
215
+ def failure?
216
+ body["results"].any? { |r| r["type"] == "error" }
217
+ end
218
+
219
+ def results
220
+ body["results"]
221
+ end
222
+ end
223
+ end
224
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: libsql-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Leon Cruz
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-05-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A Libsql client for Ruby
14
+ email:
15
+ - leon.cruz.teixeira@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rspec"
21
+ - ".rubocop.yml"
22
+ - LICENSE
23
+ - README.md
24
+ - Rakefile
25
+ - lib/libsql-rb.rb
26
+ - lib/libsql.rb
27
+ - lib/libsql/version.rb
28
+ homepage:
29
+ licenses: []
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 3.1.0
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubygems_version: 3.5.3
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: A Libsql client for Ruby
50
+ test_files: []