activerecord-cockroachdb-adapter 6.0.0beta2 → 6.1.0.pre.beta.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2d3e8ca47a966406dd46a2516ee8e28d4634109974b012604efa5eeef41661c
4
- data.tar.gz: 78f2720ce377db8b5d61c830c2f8819f4a928bceee3121668348293944b459ac
3
+ metadata.gz: 5bc55cc2cb9bfa57e9e0d5757aa2e4e892361b68f9f216de0627d3fc01c165dd
4
+ data.tar.gz: 36582fada5f267b47c827c6b30112c8db955cac8606b3ae5d9c3f73cb77c9424
5
5
  SHA512:
6
- metadata.gz: 7928d308947df985eac1ecf4ac2338d8ed5e079c2436562a822b149699b0bc42e38411e90a73a5623c376613ca610060d68f9040d823b9fa65d2869baa98a6fd
7
- data.tar.gz: 5421bb06b3185d331a0a43e700e2475f6ce28ee38d433ba28e3e82c4a0e361a94518dd46fd41b54d1a83f1a217d19e098eef0a251ad7e79803263c08bfd7e3dc
6
+ metadata.gz: c23656edaccbd223d6573d1a3636263d779ae437b5628d657cd5ed2c0d7773dfa75ea52fd7b228cf06fce477ee35ea9179fa612716dc9d5bbf893d78a8dee797
7
+ data.tar.gz: 566d24599b0a74edec6bdba172639130b4d6a92dc688870ee9848d124f4523ea400bf334bfa017e92cdc289f39e06c951301f1a8deb2dc74e7b0889cd617b08e
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 6.1.0-beta.3 - 2021-04-02
4
+
5
+ - Added a configuration option named `use_follower_reads_for_type_introspection`.
6
+ If true, it improves the speed of type introspection by allowing potentially stale
7
+ type metadata to be read. Defaults to false.
8
+
9
+ ## 6.1.0-beta.2 - 2021-03-06
10
+
11
+ - Improved connection performance by refactoring an introspection
12
+ that loads types.
13
+ - Changed version numbers to semver.
14
+
15
+ ## 6.1.0beta1
16
+
17
+ - Initial support for Rails 6.1.
18
+ - Support for spatial functionality.
data/CONTRIBUTING.md CHANGED
@@ -78,6 +78,26 @@ RAILS_SOURCE="path/to/local_copy" bundle exec rake test
78
78
 
79
79
  `test/config.yml` assumes CockroachDB will be running at localhost:26257 with a root user. Make changes to `test/config.yml` as needed.
80
80
 
81
+ ### Run Tests from a Backup
82
+
83
+ Loading the full test schema every time a test runs can take a while, so for cases where loading the schema sequentially is unimportant, it is possible to use a backup to set up the database. This is significantly faster than the standard method and is provided to run individual tests faster, but should not be used to validate a build.
84
+
85
+ First create the template database.
86
+
87
+ ```bash
88
+ bundle exec rake db:create_test_template
89
+ ```
90
+
91
+ This will create a template database for the current version (ex. `activerecord_test_template611` for version 6.1.1) and create a `BACKUP` in the `nodelocal://self/activerecord-crdb-adapter/#{activerecord_version}` directory.
92
+
93
+ To load from the template, use the `COCKROACH_LOAD_FROM_TEMPLATE` flag.
94
+
95
+ ```bash
96
+ COCKROACH_LOAD_FROM_TEMPLATE=1 TEST_FILES="test/cases/adapters/postgresql/ddl_test.rb" bundle exec rake test
97
+ ```
98
+
99
+ And the `activerecord_unittest` database will use the `RESTORE` command to load the schema from the template database.
100
+
81
101
  # Improvements
82
102
 
83
103
 
data/README.md CHANGED
@@ -22,6 +22,303 @@ development:
22
22
  user: <username>
23
23
  ```
24
24
 
25
+ ## Configuration
26
+
27
+ In addition to the standard adapter settings, CockroachDB also supports the following:
28
+
29
+ - `use_follower_reads_for_type_introspection`: Use follower reads on queries to the `pg_type` catalog when set to `true`. This helps to speed up initialization by reading historical data, but may not find recently created user-defined types.
30
+
31
+ ## Working with Spatial Data
32
+
33
+ The adapter uses [RGeo](https://github.com/rgeo/rgeo) and [RGeo-ActiveRecord](https://github.com/rgeo/rgeo-activerecord) to represent geometric and geographic data as Ruby objects and easily interface them with the adapter. The following is a brief introduction to RGeo and tips to help setup your spatial application. More documentation about RGeo can be found in the [YARD Docs](https://rubydoc.info/github/rgeo/rgeo) and [wiki](https://github.com/rgeo/rgeo/wiki).
34
+
35
+ ### Installing RGeo
36
+
37
+ RGeo can be installed with the following command:
38
+
39
+ ```sh
40
+ gem install rgeo
41
+ ```
42
+
43
+ The best way to use RGeo is with GEOS support. If you have a version of libgeos installed, you can check that it was properly linked with RGeo by running the following commands:
44
+
45
+ ```rb
46
+ require 'rgeo'
47
+
48
+ RGeo::Geos.supported?
49
+ #=> true
50
+ ```
51
+
52
+ If this is `false`, you may need to specify the GEOS directory while installing. Here's an example linking it to the CockroachDB GEOS binary.
53
+
54
+ ```sh
55
+ gem install rgeo -- --with-geos-dir=/path/to/cockroach/lib/
56
+ ```
57
+
58
+ ### Working with RGeo
59
+
60
+ RGeo uses [factories](https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)) to create geometry objects and define their properties. Different factories define their own implementations for standard methods. For instance, the `RGeo::Geographic.spherical_factory` accepts latitudes and longitues as its coordinates and does computations on a spherical surface, while `RGeo::Cartesian.factory` implements geometry objects on a plane.
61
+
62
+ The factory (or factories) you choose to use will depend on the requirements of your application and what you need to do with the geometries they produce. For example, if you are working with points or other simple geometries across long distances and need precise results, the spherical factory is a good choice. If you're working with polygons or multipolygons and analyzing complex relationships between them (`intersects?`, `difference`, etc.), then using a cartesian factory backed by GEOS is a much better option.
63
+
64
+ Once you've selected a factory, you need to create objects. RGeo supports geometry creation through standard constructors (`point`, `line_string`, `polygon`, etc.) or by WKT and WKB.
65
+
66
+ ```rb
67
+ require 'rgeo'
68
+ factory = RGeo::Cartesian.factory(srid: 3857)
69
+
70
+ # Create a line_string from points
71
+ pt1 = factory.point(0,0)
72
+ pt2 = factory.point(1,1)
73
+ pt3 = factory.point(2,2)
74
+ line_string = factory.line_string([pt1,pt2,pt3])
75
+
76
+ p line_string.length
77
+ #=> 2.8284271247461903
78
+
79
+ # check line_string equality
80
+ line_string2 = factory.parse_wkt("LINESTRING (0 0, 1 1, 2 2)")
81
+ p line_string == line_string2
82
+ #=> true
83
+
84
+ # create polygon and test intersection with line_string
85
+ pt4 = factory.point(0,2)
86
+ outer_ring = factory.linear_ring([pt1,pt2,pt3,pt4,pt1])
87
+ poly = factory.polygon(outer_ring)
88
+
89
+ p line_string.intersects? poly
90
+ #=> true
91
+ ```
92
+ ### Creating Spatial Tables
93
+
94
+ To store spatial data, you must create a column with a spatial type. PostGIS
95
+ provides a variety of spatial types, including point, linestring, polygon, and
96
+ different kinds of collections. These types are defined in a standard produced
97
+ by the Open Geospatial Consortium. You can specify options indicating the coordinate system and number of coordinates for the values you are storing.
98
+
99
+ The adapter extends ActiveRecord's migration syntax to
100
+ support these spatial types. The following example creates five spatial
101
+ columns in a table:
102
+
103
+ ```rb
104
+ create_table :my_spatial_table do |t|
105
+ t.column :shape1, :geometry
106
+ t.geometry :shape2
107
+ t.line_string :path, srid: 3857
108
+ t.st_point :lonlat, geographic: true
109
+ t.st_point :lonlatheight, geographic: true, has_z: true
110
+ end
111
+ ```
112
+
113
+ The first column, "shape1", is created with type "geometry". This is a general
114
+ "base class" for spatial types; the column declares that it can contain values
115
+ of _any_ spatial type.
116
+
117
+ The second column, "shape2", uses a shorthand syntax for the same type as the shape1 column.
118
+ You can create a column either by invoking `column` or invoking the name of the type directly.
119
+
120
+ The third column, "path", has a specific geometric type, `line_string`. It
121
+ also specifies an SRID (spatial reference ID) that indicates which coordinate
122
+ system it expects the data to be in. The column now has a "constraint" on it;
123
+ it will accept only LineString data, and only data whose SRID is 3857.
124
+
125
+ The fourth column, "lonlat", has the `st_point` type, and accepts only Point
126
+ data. Furthermore, it declares the column as "geographic", which means it
127
+ accepts longitude/latitude data, and performs calculations such as distances
128
+ using a spheroidal domain.
129
+
130
+ The fifth column, "lonlatheight", is a geographic (longitude/latitude) point
131
+ that also includes a third "z" coordinate that can be used to store height
132
+ information.
133
+
134
+ The following are the data types understood by PostGIS and exposed by
135
+ the adapter:
136
+
137
+ - `:geometry` -- Any geometric type
138
+ - `:st_point` -- Point data
139
+ - `:line_string` -- LineString data
140
+ - `:st_polygon` -- Polygon data
141
+ - `:geometry_collection` -- Any collection type
142
+ - `:multi_point` -- A collection of Points
143
+ - `:multi_line_string` -- A collection of LineStrings
144
+ - `:multi_polygon` -- A collection of Polygons
145
+
146
+ Following are the options understood by the adapter:
147
+
148
+ - `:geographic` -- If set to true, create a PostGIS geography column for
149
+ longitude/latitude data over a spheroidal domain; otherwise create a
150
+ geometry column in a flat coordinate system. Default is false. Also
151
+ implies :srid set to 4326.
152
+ - `:srid` -- Set a SRID constraint for the column. Default is 4326 for a
153
+ geography column, or 0 for a geometry column. Note that PostGIS currently
154
+ (as of version 2.0) requires geography columns to have SRID 4326, so this
155
+ constraint is of limited use for geography columns.
156
+ - `:has_z` -- Specify that objects in this column include a Z coordinate.
157
+ Default is false.
158
+ - `:has_m` -- Specify that objects in this column include an M coordinate.
159
+ Default is false.
160
+
161
+ To create a PostGIS spatial index, add `using: :gist` to your index:
162
+
163
+ ```rb
164
+ add_index :my_table, :lonlat, using: :gist
165
+
166
+ # or
167
+
168
+ change_table :my_table do |t|
169
+ t.index :lonlat, using: :gist
170
+ end
171
+ ```
172
+ ### Configuring ActiveRecord
173
+
174
+ ActiveRecord's usefulness stems from the way it automatically configures
175
+ classes based on the database structure and schema. If a column in the
176
+ database has an integer type, ActiveRecord automatically casts the data to a
177
+ Ruby Integer. In the same way, the adapter automatically
178
+ casts spatial data to a corresponding RGeo data type.
179
+
180
+ RGeo offers more flexibility in its type system than can be
181
+ interpreted solely from analyzing the database column. For example, you can
182
+ configure RGeo objects to exhibit certain behaviors related to their
183
+ serialization, validation, coordinate system, or computation. These settings
184
+ are embodied in the RGeo factory associated with the object.
185
+
186
+ You can configure the adapter to use a particular factory (i.e. a
187
+ particular combination of settings) for data associated with each type in
188
+ the database.
189
+
190
+ Here's an example using a Geos default factory:
191
+
192
+ ```ruby
193
+ RGeo::ActiveRecord::SpatialFactoryStore.instance.tap do |config|
194
+ # By default, use the GEOS implementation for spatial columns.
195
+ config.default = RGeo::Geos.factory_generator
196
+
197
+ # But use a geographic implementation for point columns.
198
+ config.register(RGeo::Geographic.spherical_factory(srid: 4326), geo_type: "point")
199
+ end
200
+ ```
201
+
202
+ The default spatial factory for geographic columns is `RGeo::Geographic.spherical_factory`.
203
+ The default spatial factory for cartesian columns is `RGeo::Cartesian.preferred_factory`.
204
+ You do not need to configure the `SpatialFactoryStore` if these defaults are ok.
205
+
206
+ More information about configuration options for the `SpatialFactoryStore` can be found in the [rgeo-activerecord](https://github.com/rgeo/rgeo-activerecord#spatial-factories-for-columns) docs.
207
+
208
+ ### Reading and Writing Spatial Columns
209
+
210
+ When you access a spatial attribute on your ActiveRecord model, it is given to
211
+ you as an RGeo geometry object (or nil, for attributes that allow null
212
+ values). You can then call the RGeo api on the object. For example, consider
213
+ the MySpatialTable class we worked with above:
214
+
215
+ ```rb
216
+ record = MySpatialTable.find(1)
217
+ point = record.lonlat # Returns an RGeo::Feature::Point
218
+ p point.x # displays the x coordinate
219
+ p point.geometry_type.type_name # displays "Point"
220
+ ```
221
+
222
+ The RGeo factory for the value is determined by how you configured the
223
+ ActiveRecord class, as described above. In this case, we explicitly set a
224
+ spherical factory for the `:lonlat` column:
225
+
226
+ ```rb
227
+ factory = point.factory # returns a spherical factory
228
+ ```
229
+
230
+ You can set a spatial attribute by providing an RGeo geometry object, or by
231
+ providing the WKT string representation of the geometry. If a string is
232
+ provided, the adapter will attempt to parse it as WKT and
233
+ set the value accordingly.
234
+
235
+ ```rb
236
+ record.lonlat = 'POINT(-122 47)' # sets the value to the given point
237
+ ```
238
+
239
+ If the WKT parsing fails, the value currently will be silently set to nil. In
240
+ the future, however, this will raise an exception.
241
+
242
+ ```rb
243
+ record.lonlat = 'POINT(x)' # sets the value to nil
244
+ ```
245
+
246
+ If you set the value to an RGeo object, the factory needs to match the factory
247
+ for the attribute. If the factories do not match, the adapter
248
+ will attempt to cast the value to the correct factory.
249
+
250
+ ```rb
251
+ p2 = factory.point(-122, 47) # p2 is a point in a spherical factory
252
+ record.lonlat = p2 # sets the value to the given point
253
+ record.shape1 = p2 # shape1 uses a flat geos factory, so it
254
+ # will cast p2 into that coordinate system
255
+ # before setting the value
256
+ record.save
257
+ ```
258
+
259
+ If you attempt to set the value to the wrong type, such as setting a linestring attribute to a point value, you will get an exception from the database when you attempt to save the record.
260
+
261
+ ```rb
262
+ record.path = p2 # This will appear to work, but...
263
+ record.save # This will raise an exception from the database
264
+ ```
265
+
266
+ ### Spatial Queries
267
+
268
+ You can create simple queries based on representational equality in the same
269
+ way you would on a scalar column:
270
+
271
+ ```ruby
272
+ record2 = MySpatialTable.where(:lonlat => factory.point(-122, 47)).first
273
+ ```
274
+
275
+ You can also use WKT:
276
+
277
+ ```ruby
278
+ record3 = MySpatialTable.where(:lonlat => 'POINT(-122 47)').first
279
+ ```
280
+
281
+ Note that these queries use representational equality, meaning they return
282
+ records where the lonlat value matches the given value exactly. A 0.00001
283
+ degree difference would not match, nor would a different representation of the
284
+ same geometry (like a multi_point with a single element). Equality queries
285
+ aren't generally all that useful in real world applications. Typically, if you
286
+ want to perform a spatial query, you'll look for, say, all the points within a
287
+ given area. For those queries, you'll need to use the standard spatial SQL
288
+ functions provided by PostGIS.
289
+
290
+ To perform more advanced spatial queries, you can use the extended Arel interface included in the adapter. The functions accept WKT strings or RGeo features.
291
+
292
+ ```rb
293
+ point = RGeo::Geos.factory(srid: 0).point(1,1)
294
+
295
+ # Example Building model where geom is a column of polygons.
296
+ buildings = Building.arel_table
297
+ containing_buiildings = Building.where(buildings[:geom].st_contains(point))
298
+ ```
299
+
300
+ See the [rgeo-activerecord YARD Docs](https://rubydoc.info/github/rgeo/rgeo-activerecord/RGeo/ActiveRecord/SpatialExpressions) for a list of available PostGIS functions.
301
+
302
+ ### Validation Issues
303
+
304
+ If you see an `RGeo::Error::InvalidGeometry (LinearRing failed ring test)` message while loading data or creating geometries, this means that the geometry you are trying to instantiate is not topologically valid. This is usually due to self-intersections in the geometry. The default behavior of RGeo factories is to raise this error when an invalid geometry is being instansiated, but this can be ignored by setting the `uses_lenient_assertions` flag to `true` when creating your factory.
305
+
306
+ ```rb
307
+ regular_fac = RGeo::Geographic.spherical_factory
308
+ modified_fac = RGeo::Geographic.spherical_factory(uses_lenient_assertions: true)
309
+
310
+ wkt = "POLYGON (0 0, 1 1, 0 1, 1 0, 0 0)" # closed ring with self intersection
311
+
312
+ regular_fac.parse_wkt(wkt)
313
+ #=> RGeo::Error::InvalidGeometry (LinearRing failed ring test)
314
+
315
+ p modified_fac.parse_wkt(wkt)
316
+ #=> #<RGeo::Geographic::SphericalPolygonImpl>
317
+ ```
318
+
319
+ Be careful when performing calculations on potentially invalid geometries, as the results might be nonsensical. For example, the area returned of an hourglass made of 2 equivalent triangles with a self-intersection in the middle is 0.
320
+
321
+ Note that when using the `spherical_factory`, there is a chance that valid geometries will be interpreted as invalid due to floating point issues with small geometries.
25
322
 
26
323
  ## Modifying the adapter?
27
324
 
data/Rakefile CHANGED
@@ -2,10 +2,30 @@ require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
  require_relative 'test/support/paths_cockroachdb'
4
4
  require_relative 'test/support/rake_helpers'
5
+ require_relative 'test/support/template_creator'
5
6
 
6
7
  task test: ["test:cockroachdb"]
7
8
  task default: [:test]
8
9
 
10
+ namespace :db do
11
+ task "create_test_template" do
12
+ ENV['DEBUG_COCKROACHDB_ADAPTER'] = "1"
13
+ ENV['COCKROACH_SKIP_LOAD_SCHEMA'] = "1"
14
+ ENV["ARCONN"] = "cockroachdb"
15
+
16
+ TemplateCreator.connect
17
+ require_relative 'test/cases/helper'
18
+
19
+ # TODO: look into this more, but for some reason the blob alias
20
+ # is not defined while running this task.
21
+ ActiveRecord::ConnectionAdapters::CockroachDB::TableDefinition.class_eval do
22
+ alias :blob :binary
23
+ end
24
+
25
+ TemplateCreator.create_test_template
26
+ end
27
+ end
28
+
9
29
  namespace :test do
10
30
  Rake::TestTask.new("cockroachdb") do |t|
11
31
  t.libs = ARTest::CockroachDB.test_load_paths
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "activerecord-cockroachdb-adapter"
7
- spec.version = "6.0.0beta2"
7
+ spec.version = "6.1.0-beta.3"
8
8
  spec.licenses = ["Apache-2.0"]
9
9
  spec.authors = ["Cockroach Labs"]
10
10
  spec.email = ["cockroach-db@googlegroups.com"]
@@ -13,8 +13,9 @@ Gem::Specification.new do |spec|
13
13
  spec.description = "Allows the use of CockroachDB as a backend for ActiveRecord and Rails apps."
14
14
  spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter"
15
15
 
16
- spec.add_dependency "activerecord", "~> 6.0.3"
17
- spec.add_dependency "pg", ">= 0.20"
16
+ spec.add_dependency "activerecord", "~> 6.1"
17
+ spec.add_dependency "pg", "~> 1.2"
18
+ spec.add_dependency "rgeo-activerecord", "~> 7.0.0"
18
19
 
19
20
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
20
21
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -21,7 +21,7 @@ run_cockroach() {
21
21
  cockroach quit --insecure || true
22
22
  rm -rf cockroach-data
23
23
  # Start CockroachDB.
24
- cockroach start-single-node --max-sql-memory=25% --cache=25% --insecure --host=localhost --listening-url-file="$urlfile" >/dev/null 2>&1 &
24
+ cockroach start-single-node --max-sql-memory=25% --cache=25% --insecure --host=localhost --spatial-libs=./cockroach-$VERSION.linux-amd64/lib --listening-url-file="$urlfile" >/dev/null 2>&1 &
25
25
  # Ensure CockroachDB is stopped on script exit.
26
26
  trap "echo 'Exit routine: Killing CockroachDB.' && kill -9 $! &> /dev/null" EXIT
27
27
  # Wait until CockroachDB has started.
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RGeo
4
+ module ActiveRecord
5
+ ##
6
+ # Extend rgeo-activerecord visitors to use PostGIS specific functionality
7
+ module SpatialToPostGISSql
8
+ def visit_in_spatial_context(node, collector)
9
+ # Use ST_GeomFromEWKT for EWKT geometries
10
+ if node.is_a?(String) && node =~ /SRID=[\d+]{0,};/
11
+ collector << "#{st_func('ST_GeomFromEWKT')}(#{quote(node)})"
12
+ else
13
+ super(node, collector)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ RGeo::ActiveRecord::SpatialToSql.prepend RGeo::ActiveRecord::SpatialToPostGISSql
20
+
21
+ module Arel # :nodoc:
22
+ module Visitors # :nodoc:
23
+ class CockroachDB < PostgreSQL # :nodoc:
24
+ include RGeo::ActiveRecord::SpatialToSql
25
+ end
26
+ end
27
+ end