pglite 0.0.1 → 0.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd89d41511dc03df457fb82af88ba097268b63aefbeabf4d92d62bad6a08db72
4
- data.tar.gz: e23f6078cb1943262b7c2eade317712900b8b3a9c76afc7fa290771c71be7875
3
+ metadata.gz: 92b24a6ef1d8a46262cebcd4f9e037ff6c4b1a66d0380595e5652552548863d9
4
+ data.tar.gz: 20fcd558fc528712767d3a9d83792fd61cb1622796f868c4f2cfb3410fcb68cb
5
5
  SHA512:
6
- metadata.gz: b0cf8e3e332546c80f6341553e8ce3fcad9adb5807747205e37a4cc80cc9f514f8dae983e4316b73f9653730282522b8b40b064cf659c7110b588378573abe3b
7
- data.tar.gz: ec223bc30ec7617e9d5bdb79083a0e4d2f024a5d8f7fca9ba2d13ac2997edc1dfbf574988e775a9924c6031eeb58745964a21e3b3c0c9a4ca24f160c402146fc
6
+ metadata.gz: 8f25e87e147d5c238b6a4ee96f3635dbdd3d57c54c89222b954261c9294bfa840c75639cc3d77b3dbb8bfd759e99c8319f857c39bbc4bc98b9c0100e8a36039a
7
+ data.tar.gz: 8a49d44dfadd426fcd931e2ee8ba8f05f31790203674f3287b98a37b80bc7dbd8fc9f72524eeb12dc50aaae785fda32f47a56cad6da43adbb3ec665fdd70c8fe
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
1
  # Change log
2
2
 
3
3
  ## master
4
+
5
+ ## 0.1.0 (2025-12-12)
6
+
7
+ - Initial version.
data/README.md CHANGED
@@ -1,37 +1,49 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/pglite-rb.svg)](https://rubygems.org/gems/pglite-rb)
2
2
  [![Build](https://github.com/palkan/pglite-rb/workflows/Build/badge.svg)](https://github.com/palkan/pglite-rb/actions)
3
3
 
4
- # PGlite for Ruby
4
+ # PGlite for Ruby and Rails
5
5
 
6
- Let's bring [PGlite](https://pglite.dev/) to Ruby!
6
+ Let's bring [PGlite](https://pglite.dev/) to Ruby and Rails! Enjoy using PostgreSQL without dealing with databases and native libraries—great for development and testing!
7
7
 
8
8
  ## Installation
9
9
 
10
- Adding to a gem:
10
+ Add the gem to your Gemfile and run `bundle install`:
11
11
 
12
12
  ```ruby
13
- # my-cool-gem.gemspec
14
- Gem::Specification.new do |spec|
15
- # ...
16
- spec.add_dependency "pglite-rb"
17
- # ...
18
- end
13
+ # Gemfile
14
+ gem "pglite"
19
15
  ```
20
16
 
21
- Or adding to your project:
17
+ ### Supported Ruby/Rails versions
18
+
19
+ - Ruby (MRI) >= 3.4
20
+ - Rails >= 7.2
21
+
22
+ ## Usage
23
+
24
+ A minimal example:
22
25
 
23
26
  ```ruby
24
- # Gemfile
25
- gem "pglite-rb"
27
+ # First, you must provision the database (creates a bunch of PostgreSQL files in your file system)
28
+ PGlite.install!("tmp/pglite")
29
+
30
+ # Now you can perform queries!
31
+ result = PGLite.exec_query("SELECT 2 + 2 as answer")
32
+
33
+ result.row_count #=> 1
34
+ result.rows #=> [[4]]
35
+ result.columns.map(&:name) #=> ["answer"]
26
36
  ```
27
37
 
28
- ### Supported Ruby versions
38
+ ### Active Record integration
29
39
 
30
- - Ruby (MRI) >= 3.0
40
+ Specify the `pglite` adapter in your `database.yml`. Done.
31
41
 
32
- ## Usage
42
+ The database would be provisioned the first time a connection is initialized (in the `tmp/pglite` directory or in the `mount_path` if one is specified in the configuration). Alternatively, you can manually call `PGlite.install!(path)` on the application boot.
43
+
44
+ ## Configuration
33
45
 
34
- TBD
46
+ You can enable PostgreSQL logs (stdout/stderr) by set the `PGLITE_WASI_STDIO=1` environment variable.
35
47
 
36
48
  ## Contributing
37
49
 
@@ -0,0 +1,14 @@
1
+ [package]
2
+ name = "pglite_rb"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [lib]
7
+ crate-type = ["cdylib"]
8
+
9
+ [dependencies]
10
+ magnus = { version = "0.8", features = ["rb-sys"] }
11
+ rb-sys = "0.9"
12
+ pglite-oxide = { git = "https://github.com/palkan/pglite-oxide.git" }
13
+ bytes = "1.0"
14
+ hex = "0.4"
@@ -3,4 +3,4 @@
3
3
  require "mkmf"
4
4
  require "rb_sys/mkmf"
5
5
 
6
- create_rust_makefile("pglite-rb/pglite_rb_ext")
6
+ create_rust_makefile("pglite-rb/pglite_rb")
@@ -0,0 +1,11 @@
1
+ use magnus::{Error, Ruby};
2
+ mod types;
3
+ mod implementation;
4
+ mod ruby_api;
5
+
6
+ rb_sys::set_global_tracking_allocator!();
7
+
8
+ #[magnus::init]
9
+ pub fn init(ruby: &Ruby) -> Result<(), Error> {
10
+ ruby_api::init(ruby)
11
+ }
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "pg"
5
+ rescue LoadError
6
+ # Patch #gem to ignore 'pg' checks
7
+ Kernel.prepend(Module.new do
8
+ def gem(dep, *)
9
+ return if dep == "pg"
10
+ super
11
+ end
12
+ end)
13
+
14
+ # Update the $LOAD_PATH to include the pg stub
15
+ $LOAD_PATH.unshift(File.expand_path(File.join(__dir__, "pglite_shims")))
16
+ end
17
+
18
+ require "active_record/connection_adapters/postgresql_adapter"
19
+
20
+ module ActiveRecord
21
+ module ConnectionHandling # :nodoc:
22
+ def pglite_adapter_class
23
+ ConnectionAdapters::PGliteAdapter
24
+ end
25
+
26
+ def pglite_connection(config)
27
+ pglite_adapter_class.new(config)
28
+ end
29
+ end
30
+
31
+ module ConnectionAdapters
32
+ class PGliteAdapter < PostgreSQLAdapter
33
+ class << self
34
+ def database_exists?(config)
35
+ true
36
+ end
37
+
38
+ def new_client(...) = PGlite::Connection.new(...)
39
+ end
40
+
41
+ def initialize(...)
42
+ super
43
+ # Prepared statements are not currently supported
44
+ @prepared_statements = false
45
+ end
46
+
47
+ # Stub encoders/decoders
48
+ def add_pg_encoders = nil
49
+
50
+ def add_pg_decoders = nil
51
+
52
+ unless method_defined?(:query_rows)
53
+ alias_method :query_rows, :query
54
+ end
55
+
56
+ # TODO: col_description is not supported ???
57
+ def column_definitions(table_name)
58
+ query_rows(<<~SQL, "SCHEMA")
59
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
60
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
61
+ c.collname, '' AS comment,
62
+ #{supports_identity_columns? ? "attidentity" : quote("")} AS identity,
63
+ #{supports_virtual_columns? ? "attgenerated" : quote("")} as attgenerated
64
+ FROM pg_attribute a
65
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
66
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
67
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
68
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
69
+ AND a.attnum > 0 AND NOT a.attisdropped
70
+ ORDER BY a.attnum
71
+ SQL
72
+ end
73
+
74
+ ::ActiveRecord::Type.add_modifier({array: true}, PostgreSQL::OID::Array, adapter: :pglite)
75
+ ::ActiveRecord::Type.add_modifier({range: true}, PostgreSQL::OID::Range, adapter: :pglite)
76
+ ::ActiveRecord::Type.register(:bit, PostgreSQL::OID::Bit, adapter: :pglite)
77
+ ::ActiveRecord::Type.register(:bit_varying, PostgreSQL::OID::BitVarying, adapter: :pglite)
78
+ ::ActiveRecord::Type.register(:binary, PostgreSQL::OID::Bytea, adapter: :pglite)
79
+ ::ActiveRecord::Type.register(:cidr, PostgreSQL::OID::Cidr, adapter: :pglite)
80
+ ::ActiveRecord::Type.register(:date, PostgreSQL::OID::Date, adapter: :pglite)
81
+ ::ActiveRecord::Type.register(:datetime, PostgreSQL::OID::DateTime, adapter: :pglite)
82
+ ::ActiveRecord::Type.register(:decimal, PostgreSQL::OID::Decimal, adapter: :pglite)
83
+ ::ActiveRecord::Type.register(:enum, PostgreSQL::OID::Enum, adapter: :pglite)
84
+ ::ActiveRecord::Type.register(:hstore, PostgreSQL::OID::Hstore, adapter: :pglite)
85
+ ::ActiveRecord::Type.register(:inet, PostgreSQL::OID::Inet, adapter: :pglite)
86
+ ::ActiveRecord::Type.register(:interval, PostgreSQL::OID::Interval, adapter: :pglite)
87
+ ::ActiveRecord::Type.register(:jsonb, PostgreSQL::OID::Jsonb, adapter: :pglite)
88
+ ::ActiveRecord::Type.register(:money, PostgreSQL::OID::Money, adapter: :pglite)
89
+ ::ActiveRecord::Type.register(:point, PostgreSQL::OID::Point, adapter: :pglite)
90
+ ::ActiveRecord::Type.register(:legacy_point, PostgreSQL::OID::LegacyPoint, adapter: :pglite)
91
+ ::ActiveRecord::Type.register(:uuid, PostgreSQL::OID::Uuid, adapter: :pglite)
92
+ ::ActiveRecord::Type.register(:vector, PostgreSQL::OID::Vector, adapter: :pglite)
93
+ ::ActiveRecord::Type.register(:xml, PostgreSQL::OID::Xml, adapter: :pglite)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pglite/pg"
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pglite/pg"
4
+ require "pglite/result"
5
+
6
+ module PGlite
7
+ class Connection
8
+ attr_reader :mount_path
9
+
10
+ def initialize(conn_params = {})
11
+ @mount_path = conn_params[:mount_path] || File.join(Dir.pwd, "tmp", "pglite")
12
+ @prepared_statements_map = {}
13
+ PGlite.install!(mount_path)
14
+ rescue => e
15
+ raise PG::Error, e.message
16
+ end
17
+
18
+ def set_client_encoding(_)
19
+ # make no-op for now
20
+ end
21
+
22
+ def finished? = true
23
+
24
+ def transaction_status
25
+ PG::PQTRANS_IDLE
26
+ end
27
+
28
+ def escape(str) = str
29
+
30
+ def raw_query(sql, params)
31
+ # FIXME: we need to add support for prepared statements at the extension level
32
+ if params.present?
33
+ raise ArgumentError, "Prepared statements are not supported in pglite"
34
+ end
35
+
36
+ raw_res = PGlite.exec_query(sql)
37
+ result = Result.new(raw_res)
38
+ @last_result = result
39
+ result
40
+ rescue => e
41
+ raise PG::Error, e.message
42
+ end
43
+
44
+ def exec(sql)
45
+ raw_query(sql, [])
46
+ end
47
+
48
+ alias_method :query, :exec
49
+ alias_method :async_exec, :exec
50
+ alias_method :async_query, :exec
51
+
52
+ def exec_params(sql, params)
53
+ if params.empty?
54
+ return exec(sql)
55
+ end
56
+ raw_query(sql, params)
57
+ end
58
+
59
+ def exec_prepared(name, params)
60
+ sql = @prepared_statements_map[name]
61
+ exec_params(sql, params)
62
+ end
63
+
64
+ def prepare(name, sql, param_types = nil)
65
+ @prepared_statements_map[name] = sql
66
+ end
67
+
68
+ def get_last_result
69
+ @last_result
70
+ end
71
+
72
+ def reset
73
+ @prepared_statements_map = {}
74
+ end
75
+
76
+ def server_version
77
+ # The result is formed by multiplying the server's major version number by 10000 and adding the minor version number.
78
+ # For example, version 10.1 will be returned as 100001, and version 11.0 will be returned as 110000.
79
+ PGlite.database_version.match(/PostgreSQL\s(\d+)\.(\d+)/)&.then do
80
+ _1[1].to_i * 10_000 + _1[2].to_i
81
+ end
82
+ end
83
+ end
84
+ end
data/lib/pglite/pg.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "pg"
5
+ return
6
+ rescue LoadError
7
+ end
8
+
9
+ # Define minumum PG interface
10
+ module PG
11
+ PQTRANS_IDLE = 0 # (connection idle)
12
+ PQTRANS_ACTIVE = 1 # (command in progress)
13
+ PQTRANS_INTRANS = 2 # (idle, within transaction block)
14
+ PQTRANS_INERROR = 3 # (idle, within failed transaction)
15
+ PQTRANS_UNKNOWN = 4 # (cannot determine status)
16
+
17
+ class Error < StandardError; end
18
+ class ConnectionBad < Error; end
19
+ class FeatureNotSupported < Error; end
20
+
21
+ class Connection
22
+ class << self
23
+ # TODO: can we support some configuration?
24
+ def conndefaults_hash = {}
25
+
26
+ def quote_ident(str)
27
+ str = str.to_s
28
+ return '""' if str.empty?
29
+ if str =~ /[^a-zA-Z_0-9]/ || str =~ /^[0-9]/
30
+ '"' + str.gsub('"', '""') + '"'
31
+ else
32
+ str
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ # Just a stub for now
39
+ class SimpleDecoder
40
+ end
41
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PGlite
4
+ class Result
5
+ attr_reader :res
6
+
7
+ # A wrapper for a result object from Oxide
8
+ def initialize(res)
9
+ @res = res
10
+ end
11
+
12
+ def map_types!(map)
13
+ self
14
+ end
15
+
16
+ def values = res.rows
17
+
18
+ def fields = res.columns.map(&:name)
19
+
20
+ def ftype(index) = res.columns[index].oid
21
+
22
+ def fmod(index) = res.columns[index].type_modifier
23
+
24
+ def cmd_tuples = res.row_count
25
+
26
+ def ntuples = res.row_count
27
+
28
+ def clear
29
+ end
30
+
31
+ include Enumerable
32
+
33
+ def each
34
+ return to_enum(:each) unless block_given?
35
+
36
+ columns = fields
37
+ res.rows.each do |res_row|
38
+ row = {}
39
+ columns.each.with_index do |col, i|
40
+ value = res_row[i]
41
+ row[col] = value
42
+ end
43
+ yield row
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PGlite # :nodoc:
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/pglite.rb CHANGED
@@ -7,7 +7,14 @@ end
7
7
  require "pglite/version"
8
8
 
9
9
  begin
10
- require "pglite_rb_ext"
10
+ /(?<ruby_version>\d+\.\d+)/ =~ RUBY_VERSION
11
+ require_relative "#{ruby_version}/pglite_rb"
11
12
  rescue LoadError
12
- # Extension not available
13
+ require_relative "pglite_rb"
14
+ end
15
+
16
+ require "pglite/connection"
17
+
18
+ if defined?(ActiveRecord::ConnectionAdapters) && ActiveRecord::ConnectionAdapters.respond_to?(:register)
19
+ ActiveRecord::ConnectionAdapters.register("pglite", "ActiveRecord::ConnectionAdapters::PGliteAdapter", "active_record/connection_adapters/pglite_adapter")
13
20
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pglite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rb_sys
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.9.39
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.9.39
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: bundler
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -93,22 +107,26 @@ dependencies:
93
107
  - - ">="
94
108
  - !ruby/object:Gem::Version
95
109
  version: '3.9'
96
- description: Example description
110
+ description: PGlite for Ruby and Rails
97
111
  email:
98
112
  - Vladimir Dementyev
99
113
  executables: []
100
114
  extensions:
101
- - ext/extconf.rb
115
+ - ext/pglite-rb/extconf.rb
102
116
  extra_rdoc_files: []
103
117
  files:
104
118
  - CHANGELOG.md
105
119
  - LICENSE.txt
106
120
  - README.md
107
- - ext/Cargo.lock
108
- - ext/Cargo.toml
109
- - ext/extconf.rb
110
- - ext/src/lib.rs
121
+ - ext/pglite-rb/Cargo.toml
122
+ - ext/pglite-rb/extconf.rb
123
+ - ext/pglite-rb/src/lib.rs
124
+ - lib/active_record/connection_adapters/pglite_adapter.rb
125
+ - lib/active_record/connection_adapters/pglite_shims/pg.rb
111
126
  - lib/pglite.rb
127
+ - lib/pglite/connection.rb
128
+ - lib/pglite/pg.rb
129
+ - lib/pglite/result.rb
112
130
  - lib/pglite/version.rb
113
131
  homepage: https://github.com/palkan/pglite-rb
114
132
  licenses:
@@ -135,5 +153,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
153
  requirements: []
136
154
  rubygems_version: 3.6.9
137
155
  specification_version: 4
138
- summary: Example description
156
+ summary: PGlite for Ruby and Rails
139
157
  test_files: []