activerecord-cipherstash-pg-adapter 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +11 -1
- data/activerecord-cipherstash-pg-adapter.gemspec +1 -1
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/column.rb +55 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/database_statements.rb +149 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/date_time.rb +31 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/hstore.rb +72 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/type_map_initializer.rb +113 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid.rb +36 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/quoting.rb +205 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_creation.rb +80 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_definitions.rb +222 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_dumper.rb +49 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_statements.rb +794 -0
- data/lib/active_record/connection_adapters/6.1/postgres_cipherstash_adapter.rb +958 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/cidr.rb +48 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/point.rb +64 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/range.rb +115 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/uuid.rb +35 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid.rb +38 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/utils.rb +80 -0
- data/lib/active_record/connection_adapters/{cipherstash_pg_adapter.rb → 7.0/postgres_cipherstash_adapter.rb} +16 -48
- data/lib/active_record/connection_adapters/postgres_cipherstash_adapter.rb +42 -12
- data/lib/activerecord-cipherstash-pg-adapter.rb +1 -1
- data/lib/version.rb +1 -1
- metadata +89 -46
- data/lib/active_record/connection_adapters/cipherstash_pg/oid.rb +0 -38
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/explain_pretty_printer.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bit.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bit_varying.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bytea.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/cidr.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/decimal.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/enum.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/inet.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/interval.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/jsonb.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/legacy_point.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/macaddr.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/money.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/oid.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/point.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/range.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/specialized_string.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/uuid.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/vector.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/xml.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/type_metadata.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/utils.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/column.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/database_statements.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/array.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/date.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/date_time.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/hstore.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/timestamp.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/timestamp_with_time_zone.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/type_map_initializer.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/quoting.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/referential_integrity.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_creation.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_definitions.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_dumper.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_statements.rb +0 -0
- /data/lib/{active_record/connection_adapters/cipherstash_pg/cipherstash_tasks.rake → cipherstash_tasks.rake} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3eaa0c02e8ccecc531934417632599e4f91edcd67fed514f315ebef2bc424ee8
|
|
4
|
+
data.tar.gz: 369c9295c615b3468421bcef9142421ac767c4dd6ef3f4a46139ce0183adc37f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09b55773769ab21a814208fd25058a82cad19a6c047ccbc84d4a715447bdcfade7b68e399242049e0285c26d0d22725310a02cb62fc4532621d6f095d90e6a93'
|
|
7
|
+
data.tar.gz: a74e2ead6f0c3029725fbd2b4d1e0b76c228446b8b284a954ce737e8ae488743105baa0f356ad922d5fdedd23051d3059b2a6618cad30b507fe6a2554cc7b3b9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2023-04-04
|
|
4
|
+
### Added
|
|
5
|
+
|
|
6
|
+
- Support for both Rails 6 + 7.
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2023-03-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Rake task to migrate plaintext data to encrypted columns.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Rails commands not recognised.
|
|
17
|
+
|
|
3
18
|
## [0.1.0] - 2023-02-01
|
|
4
19
|
|
|
5
20
|
- Initial release
|
data/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
An adapter to allow the use of the CipherStash libpq fork for encryption of data in your PostgreSQL databases.
|
|
4
4
|
|
|
5
|
+
This adapter supports Rails 6 & 7.
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
Add this line to your application's Gemfile:
|
|
@@ -16,7 +18,7 @@ In `database.yml`, use the following adapter setting:
|
|
|
16
18
|
|
|
17
19
|
```yaml
|
|
18
20
|
development:
|
|
19
|
-
adapter:
|
|
21
|
+
adapter: postgres_cipherstash
|
|
20
22
|
# ... username, password, etc. as you would with postgres as normal.
|
|
21
23
|
```
|
|
22
24
|
|
|
@@ -26,6 +28,14 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
|
26
28
|
|
|
27
29
|
To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
28
30
|
|
|
31
|
+
In the github repo:
|
|
32
|
+
|
|
33
|
+
- Click on releases.
|
|
34
|
+
- Click on `Draft a new release`.
|
|
35
|
+
- Select the created tag from the `Choose a tag` dropdown.
|
|
36
|
+
- Click on `Generate release notes`
|
|
37
|
+
- Click on `Publish release`.
|
|
38
|
+
|
|
29
39
|
## Contributing
|
|
30
40
|
|
|
31
41
|
Bug reports and pull requests are welcome on GitHub at https://github.com/cipherstash/activerecord-cipherstash-pg-adapter
|
|
@@ -30,6 +30,6 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
spec.require_paths = ["lib"]
|
|
31
31
|
|
|
32
32
|
# Runtime dependencies here; dev+test go in Gemfile.
|
|
33
|
-
spec.add_dependency "activerecord", "
|
|
33
|
+
spec.add_dependency "activerecord", ">= 6.0.0", "< 8.0.0"
|
|
34
34
|
spec.add_dependency "cipherstash-pg", "~> 1.4.5"
|
|
35
35
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/object/blank"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module ConnectionAdapters
|
|
7
|
+
module CipherStashPG
|
|
8
|
+
class Column < ConnectionAdapters::Column # :nodoc:
|
|
9
|
+
delegate :oid, :fmod, to: :sql_type_metadata
|
|
10
|
+
|
|
11
|
+
def initialize(*, serial: nil, **)
|
|
12
|
+
super
|
|
13
|
+
@serial = serial
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def serial?
|
|
17
|
+
@serial
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def array
|
|
21
|
+
sql_type_metadata.sql_type.end_with?("[]")
|
|
22
|
+
end
|
|
23
|
+
alias :array? :array
|
|
24
|
+
|
|
25
|
+
def sql_type
|
|
26
|
+
super.delete_suffix("[]")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def init_with(coder)
|
|
30
|
+
@serial = coder["serial"]
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def encode_with(coder)
|
|
35
|
+
coder["serial"] = @serial
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ==(other)
|
|
40
|
+
other.is_a?(Column) &&
|
|
41
|
+
super &&
|
|
42
|
+
serial? == other.serial?
|
|
43
|
+
end
|
|
44
|
+
alias :eql? :==
|
|
45
|
+
|
|
46
|
+
def hash
|
|
47
|
+
Column.hash ^
|
|
48
|
+
super.hash ^
|
|
49
|
+
serial?.hash
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
CipherStashPGColumn = CipherStashPG::Column # :nodoc:
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
module DatabaseStatements
|
|
7
|
+
def explain(arel, binds = [])
|
|
8
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
|
9
|
+
CipherStashPG::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Queries the database and returns the results in an Array-like object
|
|
13
|
+
def query(sql, name = nil) # :nodoc:
|
|
14
|
+
materialize_transactions
|
|
15
|
+
mark_transaction_written_if_write(sql)
|
|
16
|
+
|
|
17
|
+
log(sql, name) do
|
|
18
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
19
|
+
@connection.async_exec(sql).map_types!(@type_map_for_results).values
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
|
25
|
+
:close, :declare, :fetch, :move, :set, :show
|
|
26
|
+
) # :nodoc:
|
|
27
|
+
private_constant :READ_QUERY
|
|
28
|
+
|
|
29
|
+
def write_query?(sql) # :nodoc:
|
|
30
|
+
!READ_QUERY.match?(sql)
|
|
31
|
+
rescue ArgumentError # Invalid encoding
|
|
32
|
+
!READ_QUERY.match?(sql.b)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Executes an SQL statement, returning a PG::Result object on success
|
|
36
|
+
# or raising a PG::Error exception otherwise.
|
|
37
|
+
# Note: the PG::Result object is manually memory managed; if you don't
|
|
38
|
+
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
|
|
39
|
+
def execute(sql, name = nil)
|
|
40
|
+
if preventing_writes? && write_query?(sql)
|
|
41
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
materialize_transactions
|
|
45
|
+
mark_transaction_written_if_write(sql)
|
|
46
|
+
|
|
47
|
+
log(sql, name) do
|
|
48
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
49
|
+
@connection.async_exec(sql)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def exec_query(sql, name = "SQL", binds = [], prepare: false)
|
|
55
|
+
execute_and_clear(sql, name, binds, prepare: prepare) do |result|
|
|
56
|
+
types = {}
|
|
57
|
+
fields = result.fields
|
|
58
|
+
fields.each_with_index do |fname, i|
|
|
59
|
+
ftype = result.ftype i
|
|
60
|
+
fmod = result.fmod i
|
|
61
|
+
case type = get_oid_type(ftype, fmod, fname)
|
|
62
|
+
when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean
|
|
63
|
+
# skip if a column has already been type casted by pg decoders
|
|
64
|
+
else types[fname] = type
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
build_result(columns: fields, rows: result.values, column_types: types)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def exec_delete(sql, name = nil, binds = [])
|
|
72
|
+
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
|
|
73
|
+
end
|
|
74
|
+
alias :exec_update :exec_delete
|
|
75
|
+
|
|
76
|
+
def sql_for_insert(sql, pk, binds) # :nodoc:
|
|
77
|
+
if pk.nil?
|
|
78
|
+
# Extract the table from the insert sql. Yuck.
|
|
79
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
|
80
|
+
pk = primary_key(table_ref) if table_ref
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if pk = suppress_composite_primary_key(pk)
|
|
84
|
+
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
super
|
|
88
|
+
end
|
|
89
|
+
private :sql_for_insert
|
|
90
|
+
|
|
91
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) # :nodoc:
|
|
92
|
+
if use_insert_returning? || pk == false
|
|
93
|
+
super
|
|
94
|
+
else
|
|
95
|
+
result = exec_query(sql, name, binds)
|
|
96
|
+
unless sequence_name
|
|
97
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
|
98
|
+
if table_ref
|
|
99
|
+
pk = primary_key(table_ref) if pk.nil?
|
|
100
|
+
pk = suppress_composite_primary_key(pk)
|
|
101
|
+
sequence_name = default_sequence_name(table_ref, pk)
|
|
102
|
+
end
|
|
103
|
+
return result unless sequence_name
|
|
104
|
+
end
|
|
105
|
+
last_insert_id_result(sequence_name)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Begins a transaction.
|
|
110
|
+
def begin_db_transaction # :nodoc:
|
|
111
|
+
execute("BEGIN", "TRANSACTION")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def begin_isolated_db_transaction(isolation) # :nodoc:
|
|
115
|
+
begin_db_transaction
|
|
116
|
+
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Commits a transaction.
|
|
120
|
+
def commit_db_transaction # :nodoc:
|
|
121
|
+
execute("COMMIT", "TRANSACTION")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Aborts a transaction.
|
|
125
|
+
def exec_rollback_db_transaction # :nodoc:
|
|
126
|
+
execute("ROLLBACK", "TRANSACTION")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
def execute_batch(statements, name = nil)
|
|
131
|
+
execute(combine_multi_statements(statements))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def build_truncate_statements(table_names)
|
|
135
|
+
["TRUNCATE TABLE #{table_names.map(&method(:quote_table_name)).join(", ")}"]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Returns the current ID of a table's sequence.
|
|
139
|
+
def last_insert_id_result(sequence_name)
|
|
140
|
+
exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def suppress_composite_primary_key(pk)
|
|
144
|
+
pk unless pk.is_a?(Array)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
module OID # :nodoc:
|
|
7
|
+
class Array < Type::Value # :nodoc:
|
|
8
|
+
include ActiveModel::Type::Helpers::Mutable
|
|
9
|
+
|
|
10
|
+
Data = Struct.new(:encoder, :values) # :nodoc:
|
|
11
|
+
|
|
12
|
+
attr_reader :subtype, :delimiter
|
|
13
|
+
delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype
|
|
14
|
+
|
|
15
|
+
def initialize(subtype, delimiter = ",")
|
|
16
|
+
@subtype = subtype
|
|
17
|
+
@delimiter = delimiter
|
|
18
|
+
|
|
19
|
+
@pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
|
|
20
|
+
@pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def deserialize(value)
|
|
24
|
+
case value
|
|
25
|
+
when ::String
|
|
26
|
+
type_cast_array(@pg_decoder.decode(value), :deserialize)
|
|
27
|
+
when Data
|
|
28
|
+
type_cast_array(value.values, :deserialize)
|
|
29
|
+
else
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def cast(value)
|
|
35
|
+
if value.is_a?(::String)
|
|
36
|
+
value = begin
|
|
37
|
+
@pg_decoder.decode(value)
|
|
38
|
+
rescue TypeError
|
|
39
|
+
# malformed array string is treated as [], will raise in PG 2.0 gem
|
|
40
|
+
# this keeps a consistent implementation
|
|
41
|
+
[]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
type_cast_array(value, :cast)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def serialize(value)
|
|
48
|
+
if value.is_a?(::Array)
|
|
49
|
+
casted_values = type_cast_array(value, :serialize)
|
|
50
|
+
Data.new(@pg_encoder, casted_values)
|
|
51
|
+
else
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def ==(other)
|
|
57
|
+
other.is_a?(Array) &&
|
|
58
|
+
subtype == other.subtype &&
|
|
59
|
+
delimiter == other.delimiter
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def type_cast_for_schema(value)
|
|
63
|
+
return super unless value.is_a?(::Array)
|
|
64
|
+
"[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def map(value, &block)
|
|
68
|
+
value.map(&block)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def changed_in_place?(raw_old_value, new_value)
|
|
72
|
+
deserialize(raw_old_value) != new_value
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def force_equality?(value)
|
|
76
|
+
value.is_a?(::Array)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
def type_cast_array(value, method)
|
|
81
|
+
if value.is_a?(::Array)
|
|
82
|
+
value.map { |item| type_cast_array(item, method) }
|
|
83
|
+
else
|
|
84
|
+
@subtype.public_send(method, value)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
module OID # :nodoc:
|
|
7
|
+
class Date < Type::Date # :nodoc:
|
|
8
|
+
def cast_value(value)
|
|
9
|
+
case value
|
|
10
|
+
when "infinity" then ::Float::INFINITY
|
|
11
|
+
when "-infinity" then -::Float::INFINITY
|
|
12
|
+
when / BC$/
|
|
13
|
+
value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) }
|
|
14
|
+
super(value.delete_suffix!(" BC"))
|
|
15
|
+
else
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
module OID # :nodoc:
|
|
7
|
+
class DateTime < Type::DateTime # :nodoc:
|
|
8
|
+
def cast_value(value)
|
|
9
|
+
case value
|
|
10
|
+
when "infinity" then ::Float::INFINITY
|
|
11
|
+
when "-infinity" then -::Float::INFINITY
|
|
12
|
+
when / BC$/
|
|
13
|
+
value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) }
|
|
14
|
+
super(value.delete_suffix!(" BC"))
|
|
15
|
+
else
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def type_cast_for_schema(value)
|
|
21
|
+
case value
|
|
22
|
+
when ::Float::INFINITY then "::Float::INFINITY"
|
|
23
|
+
when -::Float::INFINITY then "-::Float::INFINITY"
|
|
24
|
+
else super
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module ConnectionAdapters
|
|
7
|
+
module CipherStashPG
|
|
8
|
+
module OID # :nodoc:
|
|
9
|
+
class Hstore < Type::Value # :nodoc:
|
|
10
|
+
include ActiveModel::Type::Helpers::Mutable
|
|
11
|
+
|
|
12
|
+
def type
|
|
13
|
+
:hstore
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def deserialize(value)
|
|
17
|
+
if value.is_a?(::String)
|
|
18
|
+
::Hash[value.scan(HstorePair).map { |k, v|
|
|
19
|
+
v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
|
|
20
|
+
k = k.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
|
|
21
|
+
[k, v]
|
|
22
|
+
}]
|
|
23
|
+
else
|
|
24
|
+
value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def serialize(value)
|
|
29
|
+
if value.is_a?(::Hash)
|
|
30
|
+
value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ")
|
|
31
|
+
elsif value.respond_to?(:to_unsafe_h)
|
|
32
|
+
serialize(value.to_unsafe_h)
|
|
33
|
+
else
|
|
34
|
+
value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def accessor
|
|
39
|
+
ActiveRecord::Store::StringKeyedHashAccessor
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Will compare the Hash equivalents of +raw_old_value+ and +new_value+.
|
|
43
|
+
# By comparing hashes, this avoids an edge case where the order of
|
|
44
|
+
# the keys change between the two hashes, and they would not be marked
|
|
45
|
+
# as equal.
|
|
46
|
+
def changed_in_place?(raw_old_value, new_value)
|
|
47
|
+
deserialize(raw_old_value) != new_value
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
HstorePair = begin
|
|
52
|
+
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
|
|
53
|
+
unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
|
|
54
|
+
/(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def escape_hstore(value)
|
|
58
|
+
if value.nil?
|
|
59
|
+
"NULL"
|
|
60
|
+
else
|
|
61
|
+
if value == ""
|
|
62
|
+
'""'
|
|
63
|
+
else
|
|
64
|
+
'"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/array/extract"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module ConnectionAdapters
|
|
7
|
+
module CipherStashPG
|
|
8
|
+
module OID # :nodoc:
|
|
9
|
+
# This class uses the data from PostgreSQL pg_type table to build
|
|
10
|
+
# the OID -> Type mapping.
|
|
11
|
+
# - OID is an integer representing the type.
|
|
12
|
+
# - Type is an OID::Type object.
|
|
13
|
+
# This class has side effects on the +store+ passed during initialization.
|
|
14
|
+
class TypeMapInitializer # :nodoc:
|
|
15
|
+
def initialize(store)
|
|
16
|
+
@store = store
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run(records)
|
|
20
|
+
nodes = records.reject { |row| @store.key? row["oid"].to_i }
|
|
21
|
+
mapped = nodes.extract! { |row| @store.key? row["typname"] }
|
|
22
|
+
ranges = nodes.extract! { |row| row["typtype"] == "r" }
|
|
23
|
+
enums = nodes.extract! { |row| row["typtype"] == "e" }
|
|
24
|
+
domains = nodes.extract! { |row| row["typtype"] == "d" }
|
|
25
|
+
arrays = nodes.extract! { |row| row["typinput"] == "array_in" }
|
|
26
|
+
composites = nodes.extract! { |row| row["typelem"].to_i != 0 }
|
|
27
|
+
|
|
28
|
+
mapped.each { |row| register_mapped_type(row) }
|
|
29
|
+
enums.each { |row| register_enum_type(row) }
|
|
30
|
+
domains.each { |row| register_domain_type(row) }
|
|
31
|
+
arrays.each { |row| register_array_type(row) }
|
|
32
|
+
ranges.each { |row| register_range_type(row) }
|
|
33
|
+
composites.each { |row| register_composite_type(row) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def query_conditions_for_initial_load
|
|
37
|
+
known_type_names = @store.keys.map { |n| "'#{n}'" }
|
|
38
|
+
known_type_types = %w('r' 'e' 'd')
|
|
39
|
+
<<~SQL % [known_type_names.join(", "), known_type_types.join(", ")]
|
|
40
|
+
WHERE
|
|
41
|
+
t.typname IN (%s)
|
|
42
|
+
OR t.typtype IN (%s)
|
|
43
|
+
OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure
|
|
44
|
+
OR t.typelem != 0
|
|
45
|
+
SQL
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
def register_mapped_type(row)
|
|
50
|
+
alias_type row["oid"], row["typname"]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def register_enum_type(row)
|
|
54
|
+
register row["oid"], OID::Enum.new
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def register_array_type(row)
|
|
58
|
+
register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype|
|
|
59
|
+
OID::Array.new(subtype, row["typdelim"])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def register_range_type(row)
|
|
64
|
+
register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype|
|
|
65
|
+
OID::Range.new(subtype, row["typname"].to_sym)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def register_domain_type(row)
|
|
70
|
+
if base_type = @store.lookup(row["typbasetype"].to_i)
|
|
71
|
+
register row["oid"], base_type
|
|
72
|
+
else
|
|
73
|
+
warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}."
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def register_composite_type(row)
|
|
78
|
+
if subtype = @store.lookup(row["typelem"].to_i)
|
|
79
|
+
register row["oid"], OID::Vector.new(row["typdelim"], subtype)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def register(oid, oid_type = nil, &block)
|
|
84
|
+
oid = assert_valid_registration(oid, oid_type || block)
|
|
85
|
+
if block_given?
|
|
86
|
+
@store.register_type(oid, &block)
|
|
87
|
+
else
|
|
88
|
+
@store.register_type(oid, oid_type)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def alias_type(oid, target)
|
|
93
|
+
oid = assert_valid_registration(oid, target)
|
|
94
|
+
@store.alias_type(oid, target)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def register_with_subtype(oid, target_oid)
|
|
98
|
+
if @store.key?(target_oid)
|
|
99
|
+
register(oid) do |_, *args|
|
|
100
|
+
yield @store.lookup(target_oid, *args)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def assert_valid_registration(oid, oid_type)
|
|
106
|
+
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
|
|
107
|
+
oid.to_i
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "./oid/array"
|
|
4
|
+
require_relative "./oid/bit"
|
|
5
|
+
require_relative "./oid/bit_varying"
|
|
6
|
+
require_relative "./oid/bytea"
|
|
7
|
+
require_relative "./oid/cidr"
|
|
8
|
+
require_relative "./oid/date"
|
|
9
|
+
require_relative "./oid/date_time"
|
|
10
|
+
require_relative "./oid/decimal"
|
|
11
|
+
require_relative "./oid/enum"
|
|
12
|
+
require_relative "./oid/hstore"
|
|
13
|
+
require_relative "./oid/inet"
|
|
14
|
+
require_relative "./oid/interval"
|
|
15
|
+
require_relative "./oid/jsonb"
|
|
16
|
+
require_relative "./oid/macaddr"
|
|
17
|
+
require_relative "./oid/money"
|
|
18
|
+
require_relative "./oid/oid"
|
|
19
|
+
require_relative "./oid/point"
|
|
20
|
+
require_relative "./oid/legacy_point"
|
|
21
|
+
require_relative "./oid/range"
|
|
22
|
+
require_relative "./oid/specialized_string"
|
|
23
|
+
require_relative "./oid/uuid"
|
|
24
|
+
require_relative "./oid/vector"
|
|
25
|
+
require_relative "./oid/xml"
|
|
26
|
+
|
|
27
|
+
require_relative "./oid/type_map_initializer"
|
|
28
|
+
|
|
29
|
+
module ActiveRecord
|
|
30
|
+
module ConnectionAdapters
|
|
31
|
+
module CipherStashPG
|
|
32
|
+
module OID # :nodoc:
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|