chrono_model 0.5.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: da72ced6e70c0fc98ba5b78417421557dfd22708
4
+ data.tar.gz: 6f6c9b32d810cceb820f5e6b502baef95add1c26
5
+ SHA512:
6
+ metadata.gz: 1f7c0e8f7e1db361fb037295122f03562359ba497495a279e53b9aa7d48cbecac4c2396bd5cff88ea609a5a2a62cc70060d24c9e8047f9482dca27d2633e61de
7
+ data.tar.gz: 7226296b3c3e0442f9c9795e556711c8a8d7cfc963da315eaaa8f50f55c91146c356ab93c3045646c2d564086ecafb7ee2df912a0082e96942e2391bbdc56653
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
1
  --color
2
- --format progress
2
+ --format Fuubar
@@ -1,12 +1,19 @@
1
1
  rvm:
2
2
  - 1.9.3
3
3
  - 2.0.0
4
+ - 2.1.1
4
5
 
5
6
  services:
6
7
  - postgresql
7
8
 
8
9
  bundler_args: --without development
9
10
 
11
+ before_install:
12
+ - sudo service postgresql stop
13
+ - sudo apt-get update
14
+ - sudo apt-get install postgresql-9.3 postgresql-plpython-9.3
15
+ - sudo service postgresql start 9.3
16
+
10
17
  before_script:
11
18
  - psql -c "CREATE DATABASE chronomodel;" -U postgres
12
19
 
data/Gemfile CHANGED
@@ -4,11 +4,20 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :development do
7
- gem 'debugger'
8
7
  gem 'pry'
8
+ gem 'hirb'
9
+
10
+ gem(
11
+ case RUBY_VERSION.to_f
12
+ when 1.8 then 'ruby-debug'
13
+ when 1.9 then 'debugger'
14
+ else 'byebug'
15
+ end
16
+ )
9
17
  end
10
18
 
11
19
  group :test do
12
20
  gem 'rspec'
13
21
  gem 'rake'
22
+ gem 'fuubar'
14
23
  end
data/LICENSE CHANGED
@@ -1,4 +1,6 @@
1
- Copyright (c) 2012 Marcello Barnaba <m.barnaba@ifad.org>
1
+ Copyright (c) 2012-2014 Marcello Barnaba <m.barnaba@ifad.org>
2
+ Copyright (c) 2012-2014 Peter J. Brindisi <p.brindisi@ifad.org>
3
+ Copyright (c) 2012-2014 IFAD
2
4
 
3
5
  MIT License
4
6
 
data/README.md CHANGED
@@ -1,228 +1,331 @@
1
- # ChronoModel
1
+ # Temporal database system on PostgreSQL using [updatable views][], [table inheritance][] and [INSTEAD OF triggers][]. [![Build Status][build-status-badge]][build-status] [![Dependency Status][deps-status-badge]][deps-status] [![Code Climate][code-analysis-badge]][code-analysis]
2
2
 
3
- A temporal database system on PostgreSQL using
4
- [table inheritance](http://www.postgresql.org/docs/9.0/static/ddl-inherit.html) and
5
- [the rule system](http://www.postgresql.org/docs/9.0/static/rules-update.html).
3
+ ChronoModel does what Oracle sells as "Flashback Queries", but with standard
4
+ SQL on free PostgreSQL. Academically speaking, ChronoModel implements a
5
+ [Type-2 Slowly-Changing Dimension][wp-scd-2] with [history tables][wp-scd-4].
6
6
 
7
- This is a data structure for a
8
- [Slowly-Changing Dimension Type 2](http://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2)
9
- temporal database, implemented using only [PostgreSQL](http://www.postgresql.org) >= 9.0 features.
7
+ All history keeping happens inside the database system, freeing application
8
+ code from having to deal with it. ChronoModel implements all the required
9
+ features in Ruby on Rails' ORM to leverage the database temporal structure
10
+ beneath.
10
11
 
11
- [![Build Status](https://travis-ci.org/ifad/chronomodel.png?branch=master)](https://travis-ci.org/ifad/chronomodel)
12
- [![Dependency Status](https://gemnasium.com/ifad/chronomodel.png)](https://gemnasium.com/ifad/chronomodel)
13
- [![Code Climate](https://codeclimate.com/github/ifad/chronomodel.png)](https://codeclimate.com/github/ifad/chronomodel)
14
12
 
15
- All the history recording is done inside the database system, freeing the application code from
16
- having to deal with it.
13
+ ## Design
17
14
 
18
- The application model is backed by an updatable view that behaves exactly like a plain table, while
19
- behind the scenes the database redirects the queries to concrete tables using
20
- [the rule system](http://www.postgresql.org/docs/9.0/static/rules-update.html) for most of the cases,
21
- and using an `AFTER FOR EACH ROW` trigger only for the `INSERT` case on tables
22
- having a `SERIAL` primary key.
15
+ The application model is backed by an updatable view in the default `public`
16
+ schema that behaves like a plain table to any database client. When data in
17
+ manipulated on it, INSTEAD OF [triggers][] redirect the manipulations to
18
+ concrete tables.
23
19
 
24
- Current data is hold in a table in the `temporal` [schema](http://www.postgresql.org/docs/9.0/static/ddl-schemas.html),
25
- while history in hold in another table in the `history` schema. The latter
26
- [inherits](http://www.postgresql.org/docs/9.0/static/ddl-inherit.html) from the former, to get
27
- automated schema updates for free. Partitioning of history is even possible but not implemented
28
- yet.
20
+ Current data is hold in a table in the `temporal` [schema][], while history in
21
+ hold in a specular one in the `history` schema. The latter [inherits][] from
22
+ the former, to get automated schema updates and for free and other benefits.
29
23
 
30
- The updatable view is created in the default `public` schema, making it visible to Active Record.
24
+ The current time is taken using [`current_timestamp`][], so that multiple data
25
+ manipulations in the same transaction on the same records always create a
26
+ single history entry (they are _squashed_ together).
31
27
 
32
- All Active Record schema migration statements are decorated with code that handles the temporal
33
- structure by e.g. keeping the view rules in sync or dropping/recreating it when required by your
34
- migrations.
28
+ [Partitioning][] of history is also possible: this design [fits the
29
+ requirements][partitioning-excl-constraints] but it's not implemented yet.
35
30
 
36
- Data extraction at a single point in time and even `JOIN`s between temporal and non-temporal data
37
- is implemented using sub-selects and a `WHERE` generated by the provided `TimeMachine` module to
38
- be included in your models.
31
+ See [README.sql][] for a SQL example defining the machinery for a simple table.
39
32
 
40
- The `WHERE` is optimized using a spatial GiST index in which time is represented represented by
41
- boxes and the filtering is done using the overlapping (`&&`) geometry operator.
42
33
 
43
- All timestamps are (forcibly) stored in the UTC time zone, bypassing the `AR::Base.config.default_timezone`
44
- setting.
34
+ ## Active Record integration
45
35
 
46
- See [README.sql](https://github.com/ifad/chronomodel/blob/master/README.sql) for the plain SQL
47
- defining this temporal schema for a single table.
36
+ All Active Record schema migration statements are decorated with code that
37
+ handles the temporal structure by e.g. keeping the triggers in sync or
38
+ dropping/recreating it when required by your migrations.
39
+
40
+ Data extraction at a single point in time and even `JOIN`s between temporal and
41
+ non-temporal data is implemented using sub-selects and a `WHERE` generated by
42
+ the provided `TimeMachine` module to be included in your models.
43
+
44
+ The `WHERE` is optimized using [GiST indexes][] on the `tsrange` defining
45
+ record validity. Overlapping history is prevented through [exclusion
46
+ constraints][] and the [btree_gist][] extension.
47
+
48
+ All timestamps are _forcibly_ stored in as UTC, bypassing the
49
+ `default_timezone` setting.
48
50
 
49
51
 
50
52
  ## Requirements
51
53
 
52
- * Ruby &gt;= 1.9.2
53
- * Active Record &gt;= 3.2
54
- * PostgreSQL &gt;= 9.0
54
+ * Ruby >= 1.9.3
55
+ * Active Record >= 4.0
56
+ * PostgreSQL >= 9.3
57
+ * The `btree_gist` PostgreSQL extension
55
58
 
56
59
 
57
60
  ## Installation
58
61
 
59
62
  Add this line to your application's Gemfile:
60
63
 
61
- gem 'chrono_model', :git => 'git://github.com/ifad/chronomodel'
64
+ gem 'chrono_model', github: 'ifad/chronomodel'
62
65
 
63
66
  And then execute:
64
67
 
65
68
  $ bundle
66
69
 
67
70
 
68
- ## Schema creation
71
+ ## Configuration
69
72
 
70
- This library hooks all `ActiveRecord::Migration` methods to make them temporal aware.
73
+ Configure your `config/database.yml` to use the `chronomodel` adapter:
74
+
75
+ ```yaml
76
+ development:
77
+ adapter: chronomodel
78
+ username: ...
79
+ ```
80
+
81
+ ## Schema creation
71
82
 
72
- The only option added is `:temporal => true` to `create_table`:
83
+ ChronoModel hooks all `ActiveRecord::Migration` methods to make them temporal
84
+ aware.
73
85
 
74
- create_table :countries, :temporal => true do |t|
75
- t.string :common_name
76
- t.references :currency
77
- # ...
78
- end
86
+ ```ruby
87
+ create_table :countries, temporal: true do |t|
88
+ t.string :common_name
89
+ t.references :currency
90
+ # ...
91
+ end
92
+ ```
79
93
 
80
- That'll create the _current_, its _history_ child table and the _public_ view.
81
- Every other housekeeping of the temporal structure is handled behind the scenes
82
- by the other schema statements. E.g.:
94
+ This creates the _temporal_ table, its inherited _history_ one the _public_
95
+ view and all the trigger machinery. Every other housekeeping of the temporal
96
+ structure is handled behind the scenes by the other schema statements. E.g.:
83
97
 
84
- * `rename_table` - renames tables, views, sequences, indexes and rules
98
+ * `rename_table` - renames tables, views, sequences, indexes and triggers
85
99
  * `drop_table` - drops the temporal table and all dependant objects
86
- * `add_column` - adds the column to the current table and updates rules
87
- * `rename_column` - renames the current table column and updates the rules
88
- * `remove_column` - removes the current table column and updates the rules
89
- * `add_index` - creates the index in the history table as well
90
- * `remove_index` - removes the index from the history table as well
100
+ * `add_column` - adds the column to the current table and updates triggers
101
+ * `rename_column` - renames the current table column and updates the triggers
102
+ * `remove_column` - removes the current table column and updates the triggers
103
+ * `add_index` - creates the index in both _temporal_ and _history_ tables
104
+ * `remove_index` - removes the index from both tables
91
105
 
92
106
 
93
107
  ## Adding Temporal extensions to an existing table
94
108
 
95
109
  Use `change_table`:
96
110
 
97
- change_table :your_table, :temporal => true
111
+ ```ruby
112
+ change_table :your_table, temporal: true
113
+ ```
98
114
 
99
115
  If you want to also set up the history from your current data:
100
116
 
101
- change_table :your_table, :temporal => true, :copy_data => true
117
+ ```ruby
118
+ change_table :your_table, temporal: true, copy_data: true
119
+ ```
102
120
 
103
121
  This will create an history record for each record in your table, setting its
104
- validity from midnight, January 1st, 1 CE. You can set a specific validity
105
- with the `:validity` option:
122
+ validity from midnight, January 1st, 1 CE. You can set a specific validity with
123
+ the `:validity` option:
124
+
125
+ ```ruby
126
+ change_table :your_table, :temporal => true, :copy_data => true, :validity => '1977-01-01'
127
+ ```
128
+
129
+ Please note that `change_table` requires you to use *old_style* `up` and
130
+ `down` migrations. It cannot work with Rails 3-style `change` migrations.
131
+
132
+ ## Selective Journaling
133
+
134
+ By default UPDATEs only to the `updated_at` field are not recorded in the
135
+ history.
136
+
137
+ You can also choose which fields are to be journaled, passing the following
138
+ options to `create_table`:
139
+
140
+ * `:journal => %w( fld1 fld2 .. .. )` - record changes in the history only when changing specified fields
141
+ * `:no_journal => %w( fld1 fld2 .. )` - do not record changes to the specified fields
142
+ * `:full_journal => true` - record changes to *all* fields, including `updated_at`.
143
+
144
+ These options are stored as JSON in the [COMMENT][] area of the public view,
145
+ alongside with the ChronoModel version that created them.
106
146
 
107
- change_table :your_table, :temporal => true, :copy_data => true, :validity => '1977-01-01'
147
+ This is visible in `psql` if you issue a `\d+`. Example after a test run:
148
+
149
+ chronomodel=# \d+
150
+ List of relations
151
+ Schema | Name | Type | Owner | Size | Description
152
+ --------+---------------+----------+-------------+------------+-----------------------------------------------------------------
153
+ public | bars | view | chronomodel | 0 bytes | {"temporal":true,"chronomodel":"0.7.0.alpha"}
154
+ public | foos | view | chronomodel | 0 bytes | {"temporal":true,"chronomodel":"0.7.0.alpha"}
155
+ public | plains | table | chronomodel | 0 bytes |
156
+ public | test_table | view | chronomodel | 0 bytes | {"temporal":true,"journal":["foo"],"chronomodel":"0.7.0.alpha"}
108
157
 
109
158
 
110
159
  ## Data querying
111
160
 
112
- A model backed by a temporal view will behave like any other model backed by a
113
- plain table. If you want to do as-of-date queries, you need to include the
114
- `ChronoModel::TimeMachine` module in your model.
161
+ Include the `ChronoModel::TimeMachine` module in your model.
115
162
 
116
- module Country < ActiveRecord::Base
117
- include ChronoModel::TimeMachine
163
+ ```ruby
164
+ module Country < ActiveRecord::Base
165
+ include ChronoModel::TimeMachine
118
166
 
119
- has_many :compositions
120
- end
167
+ has_many :compositions
168
+ end
169
+ ```
121
170
 
122
- This will create a `Country::History` model inherited from `Country`, and it
123
- will make an `as_of` class method available to your model. E.g.:
171
+ This will create a `Country::History` model inherited from `Country`, and add
172
+ an `as_of` class method.
124
173
 
125
- Country.as_of(1.year.ago)
174
+ ```ruby
175
+ Country.as_of(1.year.ago)
176
+ ```
126
177
 
127
178
  Will execute:
128
179
 
129
- SELECT "countries".* FROM (
130
- SELECT "history"."countries".* FROM "history"."countries"
131
- WHERE box(
132
- point( date_part( 'epoch', '#{1.year.ago.utc}'::timestamp ), 0 ),
133
- point( date_part( 'epoch', '#{1.year.ago.utc}'::timestamp ), 0 )
134
- ) &&
135
- box(
136
- point( date_part( 'epoch', "history"."addresses"."valid_from" ), 0 ),
137
- point( date_part( 'epoch', "history"."addresses"."valid_to" ), 0 ),
138
- )
139
- ) AS "countries"
180
+ ```sql
181
+ SELECT "countries".* FROM (
182
+ SELECT "history"."countries".* FROM "history"."countries"
183
+ WHERE '#{1.year.ago}' <@ "history"."countries"."validity"
184
+ ) AS "countries"
185
+ ```
140
186
 
141
- This work on associations using temporal extensions as well:
187
+ The returned `ActiveRecord::Relation` will then hold and pass along the
188
+ timestamp given to the first `.as_of()` call to queries on associated entities.
189
+ E.g.:
142
190
 
143
- Country.as_of(1.year.ago).first.compositions
191
+ ```ruby
192
+ Country.as_of(1.year.ago).first.compositions
193
+ ```
144
194
 
145
195
  Will execute:
146
196
 
147
- # ... countries history query ...
148
- LIMIT 1
197
+ ```sql
198
+ SELECT "countries".*, '#{1.year.ago}' AS as_of_time FROM (
199
+ SELECT "history"."countries".* FROM "history"."countries"
200
+ WHERE '#{1.year.ago}' <@ "history"."countries"."validity"
201
+ ) AS "countries" LIMIT 1
202
+ ```
149
203
 
150
- SELECT * FROM (
151
- SELECT "history"."compositions".* FROM "history"."compositions"
152
- WHERE box(
153
- point( date_part( 'epoch', '#{above_timestamp}'::timestamp ), 0 ),
154
- point( date_part( 'epoch', '#{above_timestamp}'::timestamp ), 0 )
155
- ) &&
156
- box(
157
- point( date_part( 'epoch', "history"."addresses"."valid_from" ), 0 ),
158
- point( date_part( 'epoch', "history"."addresses"."valid_to" ), 0 ),
159
- )
160
- ) AS "compositions" WHERE country_id = X
204
+ and then, using the above fetched `as_of_time` timestamp, expand to:
161
205
 
162
- And `.joins` works as well:
206
+ ```sql
207
+ SELECT * FROM (
208
+ SELECT "history"."compositions".* FROM "history"."compositions"
209
+ WHERE '#{as_of_time}' <@ "history"."compositions"."validity"
210
+ ) AS "compositions" WHERE country_id = X
211
+ ```
163
212
 
164
- Country.as_of(1.month.ago).joins(:compositions)
213
+ `.joins` works as well:
165
214
 
166
- Will execute:
215
+ ```ruby
216
+ Country.as_of(1.month.ago).joins(:compositions)
217
+ ```
167
218
 
168
- SELECT "countries".* FROM (
169
- # .. countries history query ..
170
- ) AS "countries" INNER JOIN (
171
- # .. compositions history query ..
172
- ) AS "compositions" ON compositions.country_id = countries.id
219
+ Expands to:
173
220
 
174
- More methods are provided, see the
175
- [TimeMachine](https://github.com/ifad/chronomodel/blob/master/lib/chrono_model/time_machine.rb) source
176
- for more information.
221
+ ```sql
222
+ SELECT "countries".* FROM (
223
+ SELECT "history"."countries".* FROM "history"."countries"
224
+ WHERE '#{1.month.ago}' <@ "history"."countries"."validity"
225
+ ) AS "countries" INNER JOIN (
226
+ SELECT "history"."compositions".* FROM "history"."compositions"
227
+ WHERE '#{1.month.ago}' <@ "history"."compositions"."validity"
228
+ ) AS "compositions" ON compositions.country_id = countries.id
229
+ ```
177
230
 
231
+ More methods are provided, see the [TimeMachine][] source for more information.
178
232
 
179
233
  ## Running tests
180
234
 
181
- You need a running Postgresql instance. Create `spec/config.yml` with the
235
+ You need a running PostgreSQL 9.3 instance. Create `spec/config.yml` with the
182
236
  connection authentication details (use `spec/config.yml.example` as template).
183
237
 
184
- Run `rake`. SQL queries are logged to `spec/debug.log`. If you want to see
185
- them in your output, use `rake VERBOSE=true`.
238
+ You need to connect as a database superuser, because specs need to create the
239
+ `btree_gist` extension.
240
+
241
+ Run `rake`. SQL queries are logged to `spec/debug.log`. If you want to see them
242
+ in your output, use `rake VERBOSE=true`.
243
+
244
+ ## Usage with JSON columns
245
+
246
+ [JSON][json-type] does not provide an [equality operator][json-func].
247
+ As both unnecessary update suppression and selective journaling require
248
+ comparing the OLD and NEW rows fields, this fails by default.
249
+
250
+ ChronoModel provides a naive JSON equality operator using a naive
251
+ comparison of JSON objects [implemented in pl/python][json-opclass].
252
+
253
+ To load the opclass you can use the `ChronoModel::Json.create`
254
+ convenience method. If you don't use JSON don't bother doing this.
186
255
 
187
256
  ## Caveats
188
257
 
189
- * The rules and temporal indexes cannot be saved in schema.rb. The AR
258
+ * Rails 4 support requires disabling tsrange parsing support, as it
259
+ [is broken][r4-tsrange-broken] and [incomplete][r4-tsrange-incomplete]
260
+ as of now, mainly due to a [design clash with ruby][pg-tsrange-and-ruby].
261
+
262
+ * There is (yet) no upgrade path from [v0.5][chronomodel-0.5],
263
+ (PG 9.0-compatible, `box()` and hacks) to v0.6 and up (9.3-only, `tsrange`
264
+ and _less_ hacks).
265
+
266
+ * The triggers and temporal indexes cannot be saved in schema.rb. The AR
190
267
  schema dumper is quite basic, and it isn't (currently) extensible.
191
268
  As we're using many database-specific features, Chronomodel forces the
192
269
  usage of the `:sql` schema dumper, and included rake tasks override
193
270
  `db:schema:dump` and `db:schema:load` to do `db:structure:dump` and
194
- `db:structure:load`. Two helper tasks are also added, `db:data:dump`
195
- and `db:data:load`.
196
-
197
- * `.includes` still doesn't work, but it'll fixed.
198
-
199
- * The history queries are very verbose, they should be factored out using a
200
- `FUNCTION`.
271
+ `db:structure:load`.
272
+ Two helper tasks are also added, `db:data:dump` and `db:data:load`.
201
273
 
202
- * The migration statements extension is implemented using a Man-in-the-middle
203
- class that inherits from the PostgreSQL adapter, and that relies on some
204
- private APIs. This should be made more maintainable, maybe by requiring
205
- the use of `adapter: chronomodel` or `adapter: chrono_postgresql`.
274
+ * `.includes` is quirky when using `.as_of`.
206
275
 
207
- * Savepoints are disabled, because there is
208
- [currently](http://archives.postgresql.org/pgsql-hackers/2012-08/msg01094.php)
209
- no way to identify a subtransaction belonging to the current transaction.
210
-
211
- * The choice of using subqueries instead of [Common Table Expressions](http://www.postgresql.org/docs/9.0/static/queries-with.html)
276
+ * The choice of using subqueries instead of [Common Table Expressions][]
212
277
  was dictated by the fact that CTEs [currently acts as an optimization
213
- fence](http://archives.postgresql.org/pgsql-hackers/2012-09/msg00700.php).
214
- If it will be possible [to opt-out of the
215
- fence](http://archives.postgresql.org/pgsql-hackers/2012-10/msg00024.php)
216
- in the future, they will be probably be used again as they were [in the
217
- past](https://github.com/ifad/chronomodel/commit/18f4c4b), because the
218
- resulting queries are much more readable, and do not inhibit using
219
- `.from()` from ARel.
278
+ fence][cte-optimization-fence].
279
+ If it will be possible [to opt-out of the fence][cte-opt-out-fence] in
280
+ the future, they will be probably be used again as they were [in the
281
+ past][chronomodel-cte-impl], because the resulting queries were more
282
+ readable, and do not inhibit using `.from()` on the `AR::Relation`.
220
283
 
221
284
 
222
285
  ## Contributing
223
286
 
224
287
  1. Fork it
225
288
  2. Create your feature branch (`git checkout -b my-new-feature`)
226
- 3. Commit your changes (`git commit -am 'Added some feature'`)
289
+ 3. Commit your changes (`git commit -am 'Added some great feature'`)
227
290
  4. Push to the branch (`git push origin my-new-feature`)
228
291
  5. Create new Pull Request
292
+
293
+
294
+ [build-status]: https://travis-ci.org/ifad/chronomodel
295
+ [build-status-badge]: https://travis-ci.org/ifad/chronomodel.png?branch=master
296
+ [deps-status]: https://gemnasium.com/ifad/chronomodel
297
+ [deps-status-badge]: https://gemnasium.com/ifad/chronomodel.png
298
+ [code-analysis]: https://codeclimate.com/github/ifad/chronomodel
299
+ [code-analysis-badge]: https://codeclimate.com/github/ifad/chronomodel.png
300
+
301
+ [updatable views]: http://www.postgresql.org/docs/9.3/static/sql-createview.html#SQL-CREATEVIEW-UPDATABLE-VIEWS
302
+ [table inheritance]: http://www.postgresql.org/docs/9.3/static/ddl-inherit.html
303
+ [INSTEAD OF triggers]: http://www.postgresql.org/docs/9.3/static/sql-createtrigger.html
304
+ [wp-scd-2]: http://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2
305
+ [wp-scd-4]: http://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_4
306
+ [triggers]: http://www.postgresql.org/docs/9.3/static/trigger-definition.html
307
+ [schema]: http://www.postgresql.org/docs/9.3/static/ddl-schemas.html
308
+ [inherits]: http://www.postgresql.org/docs/9.3/static/ddl-inherit.html
309
+ [`current_timestamp`]: http://www.postgresql.org/docs/9.3/interactive/functions-datetime.html#FUNCTIONS-DATETIME-TABLE
310
+
311
+ [Partitioning]: http://www.postgresql.org/docs/9.3/static/ddl-partitioning.html)
312
+ [partitioning-excl-constraints]: http://www.postgresql.org/docs/9.3/static/ddl-partitioning.html#DDL-PARTITIONING-CONSTRAINT-EXCLUSION
313
+ [README.sql]: https://github.com/ifad/chronomodel/blob/master/README.sql
314
+ [GiST indexes]: http://www.postgresql.org/docs/9.3/static/gist.html
315
+ [exclusion constraints]: http://www.postgresql.org/docs/9.3/static/sql-createtable.html#SQL-CREATETABLE-EXCLUDE
316
+ [btree_gist]: http://www.postgresql.org/docs/9.3/static/btree-gist.html
317
+ [COMMENT]: http://www.postgresql.org/docs/9.3/static/sql-comment.html
318
+ [TimeMachine]: https://github.com/ifad/chronomodel/blob/master/lib/chrono_model/time_machine.rb
319
+
320
+ [r4-tsrange-broken]: https://github.com/rails/rails/pull/13793#issuecomment-34608093
321
+ [r4-tsrange-incomplete]: https://github.com/rails/rails/issues/14010)
322
+ [pg-tsrange-and-ruby]: https://bugs.ruby-lang.org/issues/6864
323
+ [chronomodel-0.5]: https://github.com/ifad/chronomodel/tree/c2daa0f
324
+ [Common Table Expressions]: http://www.postgresql.org/docs/9.3/static/queries-with.html
325
+ [cte-optimization-fence]: http://archives.postgresql.org/pgsql-hackers/2012-09/msg00700.php
326
+ [cte-opt-out-fence]: http://archives.postgresql.org/pgsql-hackers/2012-10/msg00024.php
327
+ [chronomodel-cte-impl]: https://github.com/ifad/chronomodel/commit/18f4c4b
328
+
329
+ [json-type]: http://www.postgresql.org/docs/9.3/static/datatype-json.html
330
+ [json-func]: http://www.postgresql.org/docs/9.3/static/functions-json.html
331
+ [json-opclass]: https://github.com/ifad/chronomodel/blob/master/sql/json_ops.sql