sequel-notion 0.2.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 +7 -0
- data/CHANGELOG.md +207 -0
- data/LICENSE.txt +21 -0
- data/README.md +470 -0
- data/lib/sequel/adapters/notion.rb +159 -0
- data/lib/sequel/notion/dataset.rb +167 -0
- data/lib/sequel/notion/dataset_aggregates.rb +82 -0
- data/lib/sequel/notion/dataset_compounds.rb +89 -0
- data/lib/sequel/notion/dataset_computed.rb +124 -0
- data/lib/sequel/notion/dataset_grouping.rb +145 -0
- data/lib/sequel/notion/dataset_having.rb +99 -0
- data/lib/sequel/notion/dataset_joins.rb +124 -0
- data/lib/sequel/notion/dataset_pages.rb +145 -0
- data/lib/sequel/notion/dataset_selection.rb +51 -0
- data/lib/sequel/notion/dataset_truncation.rb +39 -0
- data/lib/sequel/notion/discovery.rb +76 -0
- data/lib/sequel/notion/errors.rb +9 -0
- data/lib/sequel/notion/filter_comparison.rb +123 -0
- data/lib/sequel/notion/filter_compiler.rb +121 -0
- data/lib/sequel/notion/filter_constants.rb +37 -0
- data/lib/sequel/notion/filter_helpers.rb +162 -0
- data/lib/sequel/notion/filter_like.rb +143 -0
- data/lib/sequel/notion/filter_like_tokenizer.rb +42 -0
- data/lib/sequel/notion/filter_negation.rb +76 -0
- data/lib/sequel/notion/filter_nested.rb +96 -0
- data/lib/sequel/notion/filter_nulls.rb +53 -0
- data/lib/sequel/notion/filter_predicates.rb +160 -0
- data/lib/sequel/notion/filter_shape.rb +75 -0
- data/lib/sequel/notion/filter_tables.rb +111 -0
- data/lib/sequel/notion/group_accumulator.rb +55 -0
- data/lib/sequel/notion/join_output.rb +58 -0
- data/lib/sequel/notion/join_where.rb +111 -0
- data/lib/sequel/notion/model_support.rb +52 -0
- data/lib/sequel/notion/notion_file.rb +228 -0
- data/lib/sequel/notion/page_api.rb +49 -0
- data/lib/sequel/notion/registry.rb +150 -0
- data/lib/sequel/notion/request_budget.rb +64 -0
- data/lib/sequel/notion/schema.rb +74 -0
- data/lib/sequel/notion/schema_lookup.rb +82 -0
- data/lib/sequel/notion/sort_compiler.rb +63 -0
- data/lib/sequel/notion/type_map.rb +125 -0
- data/lib/sequel/notion/type_map_builders.rb +125 -0
- data/lib/sequel/notion/type_map_dates.rb +67 -0
- data/lib/sequel/notion/type_map_extractors.rb +89 -0
- data/lib/sequel/notion/version.rb +7 -0
- data/lib/sequel/notion.rb +5 -0
- metadata +132 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8a7f11227df00a9112d8b47f760985a0290807b89d7224d9e9fce4c93df7d7c9
|
|
4
|
+
data.tar.gz: 30ce794fb6a0d9fe33ac45790319539f8d0d63b2d0e8df5cce10367a566ad39a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ccc702b83af21366469e911884ba787dc56d6bc94c6e186a7cf495f695c773f9830d4f98a53ed061f6e2f95ef2078be9f4a6554768fec336cece00179d3d363a
|
|
7
|
+
data.tar.gz: 27003c0414d36f7e0088eddafb1e4dfc1a8dc568bf121bc259685097565e66fb9029def065ac11afeaeca3ccea120b824e0467538191d697a24e36c4d075bb95
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 — 2026-10-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Reads every data source property type (formulas, rollups and the
|
|
8
|
+
Notion unique ID included) and writes every writable one (title,
|
|
9
|
+
status, select, relation, people, dates and date ranges, files);
|
|
10
|
+
`nil` clears a property.
|
|
11
|
+
- Filters: `IS TRUE/FALSE`, `IN`, `LIKE` patterns, formula and rollup
|
|
12
|
+
properties, date `!=`, `Sequel[:col]` and qualified columns, bare
|
|
13
|
+
checkbox and formula columns, the Notion unique ID (`where(ID:
|
|
14
|
+
"TK-62")`, `ID > 3`, `order(:ID)`, checked live).
|
|
15
|
+
- Filters nested deeper than Notion's two levels are merged or
|
|
16
|
+
distributed into clauses, so `where(a).exclude(b: 1, c: 2)` and
|
|
17
|
+
`where(a).where((b & c) | d)` work; what cannot fit raises instead of
|
|
18
|
+
a 400.
|
|
19
|
+
- `where(id: …)` fetches pages directly; `Sequel::Model` works through
|
|
20
|
+
Sequel's cached loaders and explicit primary keys; computed
|
|
21
|
+
properties are `generated` for `skip_saving_columns`.
|
|
22
|
+
- 429 and 5xx responses are retried, honouring `Retry-After`; failures
|
|
23
|
+
raise `Sequel::DatabaseError`; requests are logged.
|
|
24
|
+
- Trashed pages are reachable by id and can be restored; discovery, the
|
|
25
|
+
search fallback and `register_all_data_sources` skip data sources in
|
|
26
|
+
the trash, which Notion's search still lists.
|
|
27
|
+
- `table_exists?`, the pagination extension and `Sequel::Rollback`
|
|
28
|
+
work. `paged_each` streams through Notion's cursor, needing no order
|
|
29
|
+
and one request per page, instead of OFFSET pages that re-read every
|
|
30
|
+
page before them.
|
|
31
|
+
- A relation Notion lists only 25 of (flagged `has_more`), or 25
|
|
32
|
+
people, is read in full from the page property endpoint, for the
|
|
33
|
+
columns a query returns (checked live with 26 relations).
|
|
34
|
+
- A rollup reads back as its value (a number, a date, or an `Array` of
|
|
35
|
+
the rolled-up values) instead of a raw hash (checked live); a rollup
|
|
36
|
+
whose function gives one value filters and sorts like one: `where(Sum:
|
|
37
|
+
7)`, `Sum > 1`, `exclude`, `IN` (checked live for a sum and a
|
|
38
|
+
`latest_date`). `IN` on a formula works too, and a formula date `!=`
|
|
39
|
+
is `before or after` instead of raising. `where(F: nil)` on a formula
|
|
40
|
+
or a rollup is `is_empty`, since Notion checks a formula's emptiness
|
|
41
|
+
whatever its result type (checked live on string and number formulas);
|
|
42
|
+
formula negations need no guard, since Notion already excludes an
|
|
43
|
+
empty formula result (checked live).
|
|
44
|
+
- `sum`, `avg`, `min`, `max`, `count(:col)`, `distinct` and
|
|
45
|
+
`distinct(:col)` (`DISTINCT ON`) work, computed in Ruby over the rows
|
|
46
|
+
the query returns, with SQL's rules for `nil` and for `distinct`
|
|
47
|
+
before `limit` (checked live).
|
|
48
|
+
- `group`, `group_and_count` and `select_group` work with `count`,
|
|
49
|
+
`sum`, `avg`, `min` and `max`, computed in Ruby with one running value
|
|
50
|
+
per group; `order`, `offset` and `limit` apply to the groups; `having`
|
|
51
|
+
filters the groups, with SQL's three-valued logic (checked live).
|
|
52
|
+
- `union` (and `all:`), `intersect` and `except` work, computed in Ruby
|
|
53
|
+
(checked live); a `select` of columns on the result applies to the
|
|
54
|
+
combined rows.
|
|
55
|
+
- `join` and `left_join` on one equality, matched in Ruby: a relation
|
|
56
|
+
matches the pages it lists, each `where` condition runs in Notion on
|
|
57
|
+
the one table it tests, and aggregates and groups keep a qualified
|
|
58
|
+
column's table (checked live through a relation). A `where` on a left
|
|
59
|
+
join's joined table drops a row whose partners all fail it, as SQL
|
|
60
|
+
does, and keeps a row with no partner only if an empty page passes it
|
|
61
|
+
(checked live).
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- Ruby 3.4 is required.
|
|
66
|
+
- Files live under `lib/sequel/notion/`, as RubyGems' naming guide asks
|
|
67
|
+
of a dashed gem name, and `require "sequel/notion"` (or Bundler's
|
|
68
|
+
auto-require) loads the adapter.
|
|
69
|
+
- Negations follow SQL: `exclude(N: 1)`, `NOT LIKE` and `NOT IN` no
|
|
70
|
+
longer match pages where the property is empty, as Notion's
|
|
71
|
+
`does_not_equal` and `does_not_contain` did, and as the date `!=`
|
|
72
|
+
already did not (checked live).
|
|
73
|
+
- A model's `save` of a loaded record sends only the changed columns,
|
|
74
|
+
instead of every column, which used to write back what a row reads
|
|
75
|
+
(a date's start only, the first 25 relations or people, rich text as
|
|
76
|
+
plain text), dropping a range's end, the other relations and the
|
|
77
|
+
formatting.
|
|
78
|
+
- `Task.last` needs an explicit order (a model adds no primary key
|
|
79
|
+
order, so it raises Sequel's `No order specified` instead of "Notion
|
|
80
|
+
cannot sort by id"); `order(:id)` and `order(:in_trash)` raise
|
|
81
|
+
`Sequel::Error` instead of sending a sort Notion rejects with a 400.
|
|
82
|
+
- Operations Notion does not compute itself (joins, groups, aggregates,
|
|
83
|
+
`distinct`, unions and the like) are computed in Ruby, only on a
|
|
84
|
+
dataset that opts in with `client_side`, which `max_requests:` can
|
|
85
|
+
cap, counting the requests Notion answers with a 200 (a rate-limited
|
|
86
|
+
attempt or a failed request counts nothing); without it they raise
|
|
87
|
+
before any request (checked live).
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- `offset`, `count`, `empty?`, `get` and `select_map`.
|
|
92
|
+
- Table names keep non-Latin letters (`"タスク"` → `:タスク`) instead of
|
|
93
|
+
normalising them all to the same empty name, which made a name lookup
|
|
94
|
+
pick another data source and broke `register_all_data_sources`. A
|
|
95
|
+
title with no letter or digit is named by its id, and a search that
|
|
96
|
+
matches two data sources raises.
|
|
97
|
+
- An exclusive date `Range` ends on the day before; an exclusive `Time`
|
|
98
|
+
range raises; a beginless `Range`, or a date `Hash` with no `:start`,
|
|
99
|
+
raises `Sequel::Error` naming the property, instead of Notion's 400
|
|
100
|
+
on a null start (checked live for the `Hash` case).
|
|
101
|
+
- A property named `id` or `in_trash` raises instead of overwriting the
|
|
102
|
+
page's own column.
|
|
103
|
+
- `where(id: [a, a])` gives one row, and an id matches itself written
|
|
104
|
+
with or without dashes.
|
|
105
|
+
- With `auto_register`, a lookup or `tables` made while another thread
|
|
106
|
+
is discovering runs its own discovery and gets a complete answer,
|
|
107
|
+
instead of reading a half-filled registry. Two data sources sharing a
|
|
108
|
+
name no longer break every lookup: only that name raises, until
|
|
109
|
+
`register_data_source` picks one. `register_data_source` keeps the id
|
|
110
|
+
as a String, so registering the same id as a Symbol and as a
|
|
111
|
+
String no longer raises, and discovery never overwrites it.
|
|
112
|
+
- Text is split into runs of 2000 UTF-16 units, the length Notion
|
|
113
|
+
checks, so a long text with emoji is no longer rejected.
|
|
114
|
+
- NaN and Infinity, in a write or a filter, a numeric string that
|
|
115
|
+
overflows to Infinity (`"1e400"`), and an external file URL that does
|
|
116
|
+
not parse raise `Sequel::Error` instead of a JSON or URI error; a
|
|
117
|
+
write names the property. A file URL with no path is named by the URL
|
|
118
|
+
rather than `"/"` or `""`. A number given as a String must be
|
|
119
|
+
decimal, in writes and filters: `"0x1A"` and `"1_000"` raise instead
|
|
120
|
+
of becoming 26 and 1000.
|
|
121
|
+
- `where(F: true)` and `exclude(F: false)` on a checkbox formula filter
|
|
122
|
+
through the formula's `checkbox` key instead of raising; a bare
|
|
123
|
+
formula column, `where(:F)` or `exclude(:F)`, filters on its checkbox
|
|
124
|
+
result instead of raising.
|
|
125
|
+
- `LIKE` with a wildcard on a multi-select, people or relation property
|
|
126
|
+
raises: Notion's `contains` there matches a whole value, so `"%ruby%"`
|
|
127
|
+
silently matched only the option `ruby`. `LIKE '%'` (wildcards only)
|
|
128
|
+
is `is_not_empty`, and `NOT LIKE '%'` `is_empty`, instead of `contains
|
|
129
|
+
""` or an error.
|
|
130
|
+
- A `nil` in a list, `where(P: [a, nil])`, means empty, as
|
|
131
|
+
`where(P: nil)` does; it used to be sent as `""`, which Notion rejects
|
|
132
|
+
for a date.
|
|
133
|
+
- `order(..., nulls: :first)` raises instead of being dropped; Notion
|
|
134
|
+
always sorts empty values last.
|
|
135
|
+
- `update` with SQL (`Sequel.lit`) raises `Sequel::Error` instead of a
|
|
136
|
+
`NoMethodError`.
|
|
137
|
+
- A file of a type the adapter does not know reads back as a
|
|
138
|
+
`Sequel::Notion::File` keeping its `raw` hash, and is written back
|
|
139
|
+
unchanged, instead of making the whole page unreadable; two such files
|
|
140
|
+
compare by that hash, since they may have no URL. `File.new` refuses an
|
|
141
|
+
unknown keyword.
|
|
142
|
+
- `transaction(rollback: :always)` raises instead of keeping every write;
|
|
143
|
+
`rollback: :reraise` re-raises `Sequel::Rollback`.
|
|
144
|
+
- Upper-case page and data source ids are lower-cased, which Notion
|
|
145
|
+
requires, including an id given to `register_data_source`: it used to
|
|
146
|
+
make every query 404, `table_exists?` false and id lookups empty.
|
|
147
|
+
- A raw `NOT` over a date equality is `before or after` instead of a
|
|
148
|
+
`does_not_equal` Notion rejects.
|
|
149
|
+
- A date with an end reads back as a `Range` of the two ISO 8601
|
|
150
|
+
strings, which a write takes as is, instead of its start alone.
|
|
151
|
+
- A lock (`for_update`) on a join or a combined query raises, as on any
|
|
152
|
+
other query, instead of being dropped.
|
|
153
|
+
- `NOT IN` on a date, created-time or edited-time property is an `and`
|
|
154
|
+
of `before or after`, as `!=` is, instead of raising.
|
|
155
|
+
- `where(P: true)` on a property that is neither a checkbox nor a
|
|
156
|
+
formula raises, as `where(:P)` does, instead of sending a bare `true`
|
|
157
|
+
as a date, number or text.
|
|
158
|
+
- A `Hash` written to a multi-select, relation or people property
|
|
159
|
+
raises instead of becoming one option or id per key/value pair.
|
|
160
|
+
- `run`, `<<`, `truncate`, the `with_sql_*` writes and schema changes
|
|
161
|
+
raise `Sequel::Error` instead of a `NoMethodError` on a missing
|
|
162
|
+
`execute`; `order(Sequel.lit(...))` raises instead of sending the SQL
|
|
163
|
+
as a property name.
|
|
164
|
+
- A `union`, `intersect` or `except` names its columns after the first
|
|
165
|
+
query and matches the other's values by position, as SQL does: a
|
|
166
|
+
second query selecting other names used to keep them, so its rows
|
|
167
|
+
read `nil` under the first query's names, and `intersect` and
|
|
168
|
+
`except` missed equal rows. Queries selecting a different number of
|
|
169
|
+
columns raise.
|
|
170
|
+
- `refresh_schema!` refreshes every name of the data source, not only
|
|
171
|
+
the one given: an alias, or the id used as a table name, kept the old
|
|
172
|
+
columns, and a `select` of a new property through it raised.
|
|
173
|
+
- A date filter value that is not a `Date`, `Time`, `DateTime` or ISO
|
|
174
|
+
8601 string raises instead of being sent as its `to_s`
|
|
175
|
+
(`where(Due: 123)` sent `"123"`).
|
|
176
|
+
- A `Complex` number, written or in a filter, raises `Sequel::Error`
|
|
177
|
+
instead of a `RangeError`.
|
|
178
|
+
- A negative unique ID in a filter (`where(ID: -5)`) raises, as a
|
|
179
|
+
negative `"-5"` already did.
|
|
180
|
+
- `where(P: [])` matches no page and sends no request, and
|
|
181
|
+
`exclude(P: [])` matches every page, as with Sequel's SQL adapters,
|
|
182
|
+
instead of raising; inside `and`, `or` and `NOT` they fold away.
|
|
183
|
+
- `where(id: [...]).order(...)` sorts the pages it fetches, as a query
|
|
184
|
+
would, instead of returning them in the list's order.
|
|
185
|
+
- Ordering rows computed in Ruby (groups, unions, joins) by a checkbox
|
|
186
|
+
sorts `false` first, as Notion does, instead of a `NoMethodError`;
|
|
187
|
+
values that cannot be compared raise `Sequel::Error`.
|
|
188
|
+
- A query run inside another's `each` block has its own `max_requests`
|
|
189
|
+
budget instead of spending the outer query's.
|
|
190
|
+
- `from_self` raises `Sequel::Error` instead of a `NoMethodError`.
|
|
191
|
+
- A data source fetch that `refresh_schema!` overtook no longer puts
|
|
192
|
+
the old properties back in the cache: it fetches again.
|
|
193
|
+
- A file named after its URL takes the path segment decoded (`a b.pdf`,
|
|
194
|
+
not `a%20b.pdf`); a segment that does not decode is kept as it is.
|
|
195
|
+
- A model keeps an Integer given to a number column (`rec.N = 5` is
|
|
196
|
+
`5`, as a read gives back) instead of Sequel's float typecast turning
|
|
197
|
+
it into `5.0`.
|
|
198
|
+
- Filtering on `id` or `in_trash` outside an id lookup
|
|
199
|
+
(`where(id: x).or(...)`) says they are the page's own columns instead
|
|
200
|
+
of "Unknown property"; `nil` on a unique ID says it is never empty.
|
|
201
|
+
- `exclude`, `NOT IN` and other negations on a rollup leave out pages
|
|
202
|
+
whose rollup is empty, as on other properties: Notion's rollup
|
|
203
|
+
`does_not_equal` matched an average over no relation (checked live).
|
|
204
|
+
|
|
205
|
+
## 0.1.0
|
|
206
|
+
|
|
207
|
+
- First version.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stéphane D'Alu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|