arcadedb 0.5.2 → 0.5.4
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/.gitignore +3 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +64 -58
- data/RAG_SCHEMA.md +239 -0
- data/README.md +130 -310
- data/examples/rag_example.rb +72 -0
- data/examples/rag_usecase_demo.rb +345 -0
- data/lib/arcade/api/primitives.rb +43 -0
- data/lib/arcade/base.rb +1 -1
- data/lib/arcade/database.rb +10 -2
- data/lib/arcade/rag_schema.rb +131 -0
- data/lib/arcade/support/model.rb +12 -1
- data/lib/arcade/timeseries.rb +211 -0
- data/lib/arcade/version.rb +1 -1
- data/lib/arcade.rb +2 -0
- data/lib/model/timeseries.rb +6 -0
- data/lib/models.rb +1 -0
- metadata +9 -6
data/README.md
CHANGED
|
@@ -1,392 +1,212 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ArcadeDB — Ruby Interface
|
|
2
2
|
|
|
3
|
-
Ruby
|
|
3
|
+
Ruby interface to [ArcadeDB](https://arcadedb.com/) via the HTTP-JSON API.
|
|
4
|
+
Responses map to a Dry::Struct based ORM: each database type is a model class; graph,
|
|
5
|
+
document and time-series queries are expressed in ruby. Includes a Query preprocessor
|
|
6
|
+
and a MATCH statement generator.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
| | |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Namespace | `Arcade` |
|
|
11
|
+
| Requires | Ruby ≥ 2.7, running ArcadeDB server (tested against 26.10.x) |
|
|
12
|
+
| Transport | HTTP/JSON (HTTPX, persistent connection, basic auth) |
|
|
13
|
+
| Model layer | Dry::Struct — not ActiveRecord |
|
|
14
|
+
| Sibling docs | `RAG_SCHEMA.md` (vector schema), `rails.md`, `CHANGELOG.md` |
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
## Quick start
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
A running AracdeDB-Instance. [Quick-Start-Guide](https://docs.arcadedb.com/#Quick-Start-Docker).
|
|
15
|
-
|
|
16
|
-
## New Project
|
|
17
|
-
|
|
18
|
-
```
|
|
18
|
+
```bash
|
|
19
19
|
mkdir project && cd project
|
|
20
|
-
bundle init
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
mkdir model
|
|
24
|
-
mkdir bin
|
|
25
|
-
```
|
|
26
|
-
copy `https://github.com/topofocus/arcadedb/blob/main/bin/console` to the `bin` directory
|
|
27
|
-
copy `https://github.com/topofocus/arcadedb/blob/main/arcade.yml` to the project root and modify to your needs
|
|
28
|
-
|
|
29
|
-
## Console
|
|
30
|
-
|
|
31
|
-
To start an interactive console, a script is provided in the bin-directory.
|
|
20
|
+
bundle init && bundle add arcadedb
|
|
21
|
+
mkdir model bin
|
|
22
|
+
# copy bin/console and arcade.yml from this repo, adapt arcade.yml
|
|
32
23
|
```
|
|
33
|
-
$ cd bin && ./console.rb t ( or "d" or "p" for Test, Development and Production environment)
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
```
|
|
25
|
+
`arcade.yml` (project root or `config/`):
|
|
37
26
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
```yaml
|
|
28
|
+
:environment:
|
|
29
|
+
:test: { dbname: playground, user: root, pass: xxx }
|
|
30
|
+
:development: { dbname: devel, user: root, pass: xxx }
|
|
31
|
+
:production: { dbname: production, user: root, pass: xxx }
|
|
32
|
+
:admin: { :host: localhost, :port: 2480, :user: root, :pass: xxx }
|
|
33
|
+
:logger: stdout
|
|
42
34
|
```
|
|
43
|
-
## Config
|
|
44
|
-
|
|
45
|
-
Add a file `arcade.yml` at the root of your program or in the `config`-dir and provide suitable databases for test, development and production environment.
|
|
46
35
|
|
|
36
|
+
Console: `bin/console t|d|p` (test environment also loads the spec model files).
|
|
37
|
+
Connection handle: always available as `Arcade::Init.db` after `Arcade::Init.connect :production`.
|
|
47
38
|
|
|
48
|
-
##
|
|
39
|
+
## Architecture
|
|
49
40
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
```
|
|
42
|
+
Arcade::Base (Dry::Struct) model layer
|
|
43
|
+
├── Arcade::Vertex +nodes/edges/traverse/match
|
|
44
|
+
├── Arcade::Edge
|
|
45
|
+
├── Arcade::Document
|
|
46
|
+
├── Arcade::Revision / RevisionRecord audit trail
|
|
47
|
+
└── Arcade::Timeseries native time-series types (0.5.4+)
|
|
53
48
|
|
|
54
|
-
|
|
49
|
+
Arcade::Database db.query / db.execute / db.transmit (transactions, model allocation)
|
|
50
|
+
Arcade::Api raw HTTP primitives (query, execute, post_text, get_json, ...)
|
|
51
|
+
```
|
|
55
52
|
|
|
56
|
-
|
|
53
|
+
Class name maps to database type via `snake_case` (`Demo::User` → `demo_user`).
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
Similar to ActiveRecord Model Objects but based on [Dry-Struct](https://dry-rb.org/gems/dry-struct/1.0/).
|
|
55
|
+
## Models
|
|
60
56
|
|
|
61
57
|
```ruby
|
|
62
|
-
#
|
|
58
|
+
# model/demo/user.rb
|
|
63
59
|
module Demo
|
|
64
60
|
class User < Arcade::Vertex
|
|
65
61
|
attribute :name, Types::Nominal::String
|
|
66
|
-
|
|
62
|
+
|
|
67
63
|
def grandparents
|
|
68
|
-
db.query(
|
|
64
|
+
db.query("select in('is_family') from #{rid}")&.allocate_model
|
|
69
65
|
end
|
|
70
66
|
end
|
|
71
67
|
end
|
|
72
68
|
__END__
|
|
73
69
|
CREATE PROPERTY demo_user.name STRING
|
|
74
|
-
CREATE INDEX
|
|
70
|
+
CREATE INDEX ON demo_user(name) UNIQUE
|
|
75
71
|
```
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
`Demo::User.create_type` creates the type and executes provided database-commands after `__END__`.
|
|
80
|
-
|
|
81
|
-
Other properties are schemaless. They are present in the database and are mapped to the `value`-attribute on the ruby side.
|
|
73
|
+
`Demo::User.create_type` creates the type and runs the `__END__` DDL.
|
|
74
|
+
Undeclared properties are schemaless — they arrive in the `values` attribute.
|
|
82
75
|
|
|
83
76
|
```ruby
|
|
84
77
|
Person.insert name: "Hubert", age: 35
|
|
85
78
|
Person.update set: { age: 36 }, where: { name: 'Hubert' }
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Person.all
|
|
91
|
-
Person.delete all: true || where: age: 56 , ...
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Nodes and Traverse
|
|
95
|
-
|
|
96
|
-
`ArcadeDB` wraps common queries of bidirectional connected vertices.
|
|
97
|
-
|
|
98
|
-
Suppose
|
|
99
|
-
```ruby
|
|
100
|
-
m = Person.insert name: 'Hubert', age: 25
|
|
101
|
-
f = Person.insert name: 'Pauline', age: 28
|
|
102
|
-
m.assign via: IsMarriedTo, vertex: f , divorced: false
|
|
103
|
-
|
|
79
|
+
Person.where("age > 40").first.update age: 37
|
|
80
|
+
Person.upsert name: 'Hugo' # ensure-exists (needs index on field)
|
|
81
|
+
Person.delete all: true # or where: {...}
|
|
104
82
|
```
|
|
105
|
-
This creates the simple graph
|
|
106
|
-
> Hubert --- is_married_to --> Pauline
|
|
107
|
-
> |
|
|
108
|
-
> -- divorced (attribute on the edge)
|
|
109
83
|
|
|
110
|
-
|
|
84
|
+
## Graph: nodes, traverse, match
|
|
111
85
|
|
|
112
86
|
```ruby
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# or simply
|
|
116
|
-
pauline = hubert.out.first
|
|
87
|
+
m = Person.insert(name: 'Hubert'); f = Person.insert(name: 'Pauline')
|
|
88
|
+
m.assign via: IsMarriedTo, vertex: f, divorced: false
|
|
117
89
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
hubert.nodes( :out, via: IsMarriedTo, where: "age < 30" )
|
|
122
|
-
```
|
|
123
|
-
gets all wives of hubert, who are younger then 30 years.
|
|
124
|
-
or
|
|
125
|
-
```ruby
|
|
126
|
-
Person.nodes( :outE, via: IsMarriedTo, where: { divorced: false } )
|
|
90
|
+
hubert = Person.find(name: 'Hubert')
|
|
91
|
+
hubert.nodes(:out, via: IsMarriedTo, where: "age < 30").first # or hubert.out.first
|
|
92
|
+
hubert.edges(via: IsMarriedTo) # edges carry attributes
|
|
127
93
|
|
|
94
|
+
start_node.traverse :out, via: Connects, depth: 100 # depth-limited walk
|
|
128
95
|
```
|
|
129
|
-
gets all wives where the divorced-condition, which is set on the edge, is false.
|
|
130
|
-
|
|
131
|
-
### Query
|
|
132
|
-
|
|
133
|
-
A **Query Preprocessor** is implemented. Its adapted from ActiveOrient. The [documentation](https://github.com/topofocus/active-orient/wiki/OrientQuery)
|
|
134
|
-
is still valid, but the class has changed to `Arcade::Query`.
|
|
135
|
-
|
|
136
|
-
### Match
|
|
137
|
-
|
|
138
|
-
A simple **Match Statement Generator** is provided for convenience. As the [declarative syntax](https://github.com/ArcadeData/arcadedb-docs/blob/main/src/main/asciidoc/sql/SQL-Match.adoc) is very intuitive, a preprocessor should not be
|
|
139
|
-
necessary. However, with the help of some ruby magic, the creation of match-statements is simply an no-brainer.
|
|
140
96
|
|
|
141
|
-
|
|
142
|
-
a = Arcade::Match.new( type: Person, as: :persons)
|
|
143
|
-
.out( IsMarriedTo )
|
|
144
|
-
.node( where: 'age < 30')
|
|
145
|
-
a.to_s
|
|
146
|
-
=> Match { type: person, as: persons }.out('is_married_to'){ where: ( age < 30) } RETURN persons
|
|
147
|
-
|
|
148
|
-
a.execute.select_results # sends the statement to the database and returns Arcade Objects.
|
|
149
|
-
```
|
|
97
|
+
MATCH generator — also from a vertex instance (handy when `params[:rid]` is given):
|
|
150
98
|
|
|
151
|
-
To query properties on Edges, `inE`, and `outE` are present.
|
|
152
99
|
```ruby
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
a.outE via: [IsMarriedTo, IsMarriedIn], where: { year: 1990..2000 }, as: :bride
|
|
158
|
-
-> .outV('is_married_in') ...
|
|
100
|
+
Arcade::Match.new(type: Person, as: :p).out(IsMarriedTo).node(where: 'age < 30').execute.select_results
|
|
101
|
+
Watchlist.match(symbol: 'iBit', as: :w).out(HasUnderlying).node(as: :u)
|
|
102
|
+
"#49:0".load_rid.match.out(HasUnderlying).node(as: :u)
|
|
159
103
|
```
|
|
160
104
|
|
|
161
|
-
|
|
162
|
-
```ruby
|
|
163
|
-
p = Person.find name: 'tux'
|
|
164
|
-
c = Person.find name: 'hilde'
|
|
165
|
-
a = Arcade::Match.new( vertex: p ) -> Match { type: person, rid: 2:0 }
|
|
166
|
-
.out( IsMarriedTo ) -> .out('is_married_to')
|
|
167
|
-
.node( as: :wife ) -> { as: wife }
|
|
168
|
-
.out( HasChild ) -> .out('has_child')
|
|
169
|
-
.node( vertex: c ) -> { type: person, rid: 2:1 }
|
|
170
|
-
-> Return wife
|
|
171
|
-
```
|
|
172
|
-
fetches the vertex between two known vertices. This is the fastest lookup mechanism if the vertices are prefetched.
|
|
173
|
-
|
|
174
|
-
Arcade::Match-objects can be used as `from:` argument to Arcade::Query-Statements; hybrid queries are
|
|
175
|
-
easily constructed without extensive string-manipulations.
|
|
176
|
-
|
|
177
|
-
#### Vertex based Match statements
|
|
178
|
-
|
|
179
|
-
A `match`-method is implemented for Vertex-Classes and vertices.
|
|
180
|
-
|
|
181
|
-
The class-method takes any argument (except `as:`) as part of a `where`-condition.
|
|
105
|
+
Query objects nest (hybrid queries without string surgery):
|
|
182
106
|
|
|
183
107
|
```ruby
|
|
184
|
-
|
|
185
|
-
m = Watchlist.match( symbol: 'iBit', as: :ibit )
|
|
186
|
-
.out( HasUnderlying)
|
|
187
|
-
.node( as: :u )
|
|
188
|
-
|
|
189
|
-
m.to_s
|
|
190
|
-
=> "MATCH { type: watchlist, where: ( symbol='iBit' ), as: ibit }
|
|
191
|
-
.out('has_underlying'){ as: u }
|
|
192
|
-
RETURN ibit,u "
|
|
108
|
+
Query.new(from: match_object, projection: 'median(note_count)', where: '$depth>=50')
|
|
193
109
|
```
|
|
194
110
|
|
|
195
|
-
|
|
196
|
-
associated nodes are easily selected
|
|
111
|
+
## TimeSeries (new in 0.5.4)
|
|
197
112
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
m= params[:rid].load_rid.match.out( HasUnterlying ).node( as: :u )
|
|
202
|
-
m.to_s
|
|
203
|
-
=> "MATCH { type: strategie, rid: #49:0 }.out('has_underlying'){ as: u } RETURN u "
|
|
204
|
-
```
|
|
205
|
-
## Low Level Database Requests
|
|
206
|
-
The **second Layer** handles Database-Requests.
|
|
207
|
-
In its actual implementation, these requests are delegated to the HTTP/JSON-API.
|
|
208
|
-
|
|
209
|
-
`Arcade::Init` uses the database specification of the `arcade.yml` file.
|
|
210
|
-
The database-handle is always present through `Arcade::Init.db`
|
|
211
|
-
|
|
212
|
-
```ruby
|
|
213
|
-
db = Arcade::Init.db
|
|
214
|
-
|
|
215
|
-
$ db.get nn, mm # nn=2; mm=0 # returns a Aracde:Base object
|
|
216
|
-
# rid is either "#11:10:" or two numbers
|
|
217
|
-
$ db.query querystring # returns either an Array of results (as Hash)
|
|
218
|
-
$ db.execute { querystring } # execute a non idempotent query within a (nested) transaction
|
|
219
|
-
$ db.create <name>, attribute: value .... # Creates a new <Document | Vertex> and returns the rid
|
|
220
|
-
# Operation is performed as Database-Transaction and is rolled back on error
|
|
221
|
-
$ db.insert <name>, attribute: value .... # Inserts a new <Document | Vertex> and returns the new object
|
|
222
|
-
$ db.create_edge <name>, from: <rid> or [rid, rid, ..] , to: <rid> or [rid, rid, ..]
|
|
223
|
-
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
**Convert database input to Arcade::Base Models**
|
|
227
|
-
|
|
228
|
-
Either `db.query` or `db.execute` return the raw JSON-input from the database. It can always converted to model-objects by chaining
|
|
229
|
-
`allocate_model` or `select_result`.
|
|
230
|
-
|
|
231
|
-
```ruby
|
|
232
|
-
$ db.query "select from my_names limit 1"
|
|
233
|
-
# which is identical to
|
|
234
|
-
$ My::Names.query( limit: 1).execute
|
|
235
|
-
=> [{:@out=>0, :@rid=>"#225:6", :@in=>0, :@type=>"my_names", :@cat=>"v", :name=>"Zaber"}]
|
|
236
|
-
# then
|
|
237
|
-
$ My::Names.query( limit: 1).execute.allocate_model.to_human
|
|
238
|
-
=> ["<my_names[#225:6]: name: Zaber>" ]
|
|
239
|
-
# replaces the hash with a My::Names Object
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
The **Base Layer** implements a low level access to the database API.
|
|
113
|
+
Native ArcadeDB time-series types: columnar, compressed, tag-partitioned.
|
|
114
|
+
**Not vertices** — no rid, no edges. Membership lives in tags, values in typed fields.
|
|
244
115
|
|
|
245
116
|
```ruby
|
|
117
|
+
# model/ohlcv.rb
|
|
118
|
+
class Ohlcv < Arcade::Timeseries
|
|
119
|
+
timestamp :ts, precision: :second # date-granular series: use :second
|
|
120
|
+
tags symbol: String, currency: String
|
|
121
|
+
fields open: Float, high: Float, low: Float, close: Float, volume: Integer
|
|
122
|
+
end
|
|
246
123
|
|
|
247
|
-
|
|
248
|
-
$ Arcade::Api.create_database <a string> # returns true if successfull
|
|
249
|
-
$ Arcade::Api.drop_database <a string> # returns true if successfull
|
|
124
|
+
Ohlcv.create_type
|
|
250
125
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
126
|
+
# write — Line Protocol under the hood, batched
|
|
127
|
+
Ohlcv.write symbol: 'SAPX:GER', currency: 'EUR', ts: Date.new(2026,10,20),
|
|
128
|
+
open: 290.0, high: 296.0, low: 290.0, close: 295.5, volume: 120
|
|
129
|
+
Ohlcv.write_many rows # array of hashes, one POST per batch
|
|
255
130
|
|
|
131
|
+
# read — SQL range pushdown
|
|
132
|
+
Ohlcv.range from: '2026-01-01', tags: { symbol: 'SAPX:GER' }
|
|
133
|
+
Ohlcv.latest tags: { symbol: 'SAPX:GER' } # => {ts:, close:, ...} or nil
|
|
256
134
|
|
|
257
|
-
|
|
258
|
-
|
|
135
|
+
# weekly candles in one query
|
|
136
|
+
Ohlcv.bucket '7d', from: Date.new(2024,10,1), tags: { symbol: 'SAPX:GER' },
|
|
137
|
+
aggregate: { close: :last, high: :max, low: :min, volume: :sum }
|
|
259
138
|
```
|
|
260
139
|
|
|
140
|
+
Accepted `ts` values: `Time`, `Date`, `DateTime`, ISO-String, epoch-seconds — normalised
|
|
141
|
+
to UTC and to the declared precision. Timezones: server timestamps are stored absolute;
|
|
142
|
+
build Date objects UTC (`Time.utc`) or comparisons drift by the host offset.
|
|
261
143
|
|
|
262
|
-
|
|
263
|
-
```ruby
|
|
264
|
-
{ :query => "<the query string> ",
|
|
265
|
-
:language => one of :sql, :cypher, :gmelin: :neo4 ,
|
|
266
|
-
:params => a hash of parameters,
|
|
267
|
-
:limit => a number ,
|
|
268
|
-
:serializer => one of :graph, :record }
|
|
269
|
-
```
|
|
144
|
+
## RAG
|
|
270
145
|
|
|
271
|
-
|
|
146
|
+
`require 'arcade/rag_schema'` — vector + full-text schema for retrieval pipelines.
|
|
147
|
+
See `RAG_SCHEMA.md` and `examples/rag_example.rb`.
|
|
272
148
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
### Ensure that a Record exists
|
|
276
|
-
|
|
277
|
-
The `upsert` command either updates or creates a database record.
|
|
149
|
+
## Transactions & audit
|
|
278
150
|
|
|
279
151
|
```ruby
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
User.upsert set: { age: 46 }, where: { name: 'Hugo' }
|
|
152
|
+
db = Arcade::Init.db
|
|
153
|
+
db.begin_transaction; ... ; db.commit # or db.rollback
|
|
283
154
|
```
|
|
284
|
-
either creates the record (and returns it) or returns the existing database entry. Obviously, the addressed attribute (where condition)
|
|
285
|
-
must have a proper index.
|
|
286
|
-
|
|
287
|
-
The `upsert` statement provides a smart method to ensure the presence of a defined starting point.
|
|
288
155
|
|
|
289
|
-
|
|
156
|
+
`Arcade::Revision` subclasses keep an append-only `protocol` stack of
|
|
157
|
+
`Arcade::RevisionRecord` entries; every `update` appends (old value + action + user).
|
|
290
158
|
|
|
291
|
-
|
|
159
|
+
## Low-level access
|
|
292
160
|
|
|
293
|
-
```ruby
|
|
294
|
-
new_vertex = ->(n) { Node.create( note_count: n ) } ## lambda to create a Node type record
|
|
295
|
-
nucleus = BaseNode.create item: 'b' ## create a start node
|
|
296
|
-
(1..10).each{ |n| nucleus.assign( via: Connects, vertex: new_vertex[n]) } ## connect nodes via Connects-Edges
|
|
297
|
-
```
|
|
298
|
-
After creating a star-like structure, the environment can be explored
|
|
299
|
-
```ruby
|
|
300
|
-
nucleus.edges.to_human
|
|
301
|
-
=>["<connects[#80:14] :.: #4:0->{}->#34:2>",
|
|
302
|
-
"<connects[#79:13] :.: #4:0->{}->#31:1>",
|
|
303
|
-
( ... )
|
|
304
|
-
"<connects[#79:14] :.: #4:0->{}->#31:2>"]
|
|
305
|
-
|
|
306
|
-
nucleus.nodes.to_human
|
|
307
|
-
=> ["<node[#34:2]: item: 10>",
|
|
308
|
-
"<node[#31:1]: item: 1>",
|
|
309
|
-
( ... )
|
|
310
|
-
"<node[#31:2]: item: 9>"]
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
Edges provide a `vertices`-method to load connected ones. Both vertices and edges expose `in`, `out`, `both`-methods to select connections further.
|
|
314
|
-
Specific edge-classes (types) are provided with the `via:` parameter, as shown in `assign` above.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
### Traverse Facility
|
|
318
|
-
|
|
319
|
-
To ease queries to the graph database, `Arcade::Query` supports traversal of nodes
|
|
320
|
-
|
|
321
|
-
Create a Chain of Nodes:
|
|
322
161
|
```ruby
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
start_node = Arcade::ExtraNode.create( item: 'linear' )
|
|
330
|
-
linear_elements start_node , 200
|
|
162
|
+
Arcade::Api.query(db, "select from x") # parsed :result (array)
|
|
163
|
+
Arcade::Api.execute(db) { "update x set ..." } # write, retry on 'Please retry'
|
|
164
|
+
Arcade::Api.get_json("ts/#{db}/latest?type=...") # raw body (ts endpoints)
|
|
165
|
+
Arcade::Api.post_text("ts/#{db}/write?precision=s", lines) # Line Protocol
|
|
166
|
+
db.query("select ...").allocate_model # hashes -> model objects
|
|
331
167
|
```
|
|
332
168
|
|
|
333
|
-
|
|
169
|
+
Query payload hash: `{ query:, language: :sql|:cypher|:gremlin|:neo4j, params:, limit:, serializer: :graph|:record }`.
|
|
334
170
|
|
|
335
|
-
|
|
336
|
-
hundred_elements = start_node.traverse :out, via: Arcade::Connects, depth: 100
|
|
337
|
-
median = Query.new from: hundred_elements,
|
|
338
|
-
projection: 'median(note_count)',
|
|
339
|
-
where: '$depth>=50'
|
|
340
|
-
median.to_s
|
|
341
|
-
-=> select median(note_count) from ( traverse out(connects) from #52:0 while $depth < 100 ) where $depth>=50
|
|
342
|
-
=> {:"median(note_count)"=>75.5 }
|
|
343
|
-
```
|
|
171
|
+
## Server & version notes (verified ArcadeDB 26.10)
|
|
344
172
|
|
|
345
|
-
|
|
173
|
+
| Observation | Consequence |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `schema:types` does not list timeseries types | existence probe: `POST /ts/{db}/query` → 400 `unknownTypes`/`does not exist` = absent (`type_exists?` uses this) |
|
|
176
|
+
| `CREATE TIMESERIES TYPE` registers asynchronously (~1 s) | `create_type` waits for visibility; a write right after create fails with `unknownTypes` otherwise |
|
|
177
|
+
| `DELETE FROM <ts-type> WHERE ...` reports a count but keeps rows (26.10.1) | no window-replace possible; re-writing a timestamp **appends** — deduplicate on read |
|
|
178
|
+
| timeseries timestamp precision defaults to NANOSECOND | declare `PRECISION` explicitly; numeric literals are interpreted per precision |
|
|
179
|
+
| `bucket` is a reserved word in SQL | aliased in backticks by `bucket()` |
|
|
180
|
+
| `IF NOT EXISTS` unsupported on `CREATE TIMESERIES TYPE` | guarded by `type_exists?` |
|
|
181
|
+
| Line Protocol needs pre-created types (`tsAutoCreateType` off by default) | `write_many` fails with `unknownTypes` if the type is missing |
|
|
182
|
+
| persistent HTTP connections die after ~3 s idle | Primitives renew the handle after 2 s inactivity |
|
|
346
183
|
|
|
347
|
-
|
|
184
|
+
## Testing
|
|
348
185
|
|
|
186
|
+
```bash
|
|
187
|
+
bundle install && bundle exec rspec # needs a reachable ArcadeDB server
|
|
188
|
+
bundle exec rspec spec/lib/timeseries_spec.rb # single file; spec/lib/* against arcade.yml :test db
|
|
349
189
|
```
|
|
350
|
-
db = Arcade::Init.db
|
|
351
|
-
db.begin_transaction
|
|
352
|
-
|
|
353
|
-
{ perform insert, update, query, etc tasks in the database }
|
|
354
|
-
|
|
355
|
-
db.commit
|
|
356
190
|
|
|
357
|
-
|
|
191
|
+
Model files for specs live in `spec/model/` (Zeitwerk-loaded). The spec suite is the
|
|
192
|
+
authoritative behaviour reference — read `spec/lib/*` before assuming API shape.
|
|
358
193
|
|
|
359
|
-
|
|
194
|
+
## Repository layout
|
|
360
195
|
|
|
361
196
|
```
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
If a vertex is based on `Arcade::Revision`, a `protocol`-property is included. Its initialised with a single `Arcade::RevisionRecord` Object. Each `update` adds an `Arcade::RevisionRecord` to the protocol stack.
|
|
371
|
-
|
|
372
|
-
```ruby
|
|
373
|
-
m= Member.insert( name: "Hubert",
|
|
374
|
-
surname: "Hugo",
|
|
375
|
-
birth: Date.new( 1976,3,15 ) ) { 'record initiated' }
|
|
376
|
-
|
|
377
|
-
m.update( birth: Date.new( 1978,3,15 ) ) { "member provided correct date via mail" }
|
|
378
|
-
|
|
379
|
-
m.protocol => Date | User | Action | old
|
|
380
|
-
--------------------------------------------------------------------------------
|
|
381
|
-
=> 1.1.2024 | root | record initiated |
|
|
382
|
-
=> 1.2.2024 | root | member provided correct date via mail | birth: "1976-3-15"
|
|
383
|
-
|
|
197
|
+
lib/arcade.rb entry point (requires below)
|
|
198
|
+
lib/arcade/ base, database, query, match, timeseries, api/, support/
|
|
199
|
+
lib/model/ base classes (vertex, edge, document, revision, timeseries)
|
|
200
|
+
lib/models.rb base-class loader
|
|
201
|
+
examples/ documented use cases
|
|
202
|
+
spec/lib, spec/model rspec suite (authoritative)
|
|
203
|
+
bin/console irb shortcut per environment
|
|
384
204
|
```
|
|
385
205
|
|
|
386
206
|
## Contributing
|
|
387
207
|
|
|
388
|
-
Bug reports and pull requests are welcome
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
208
|
+
Bug reports and pull requests are welcome — this project follows the
|
|
209
|
+
[Contributor Covenant](http://contributor-covenant.org) code of conduct
|
|
210
|
+
([detail](https://github.com/topofocus/arcadedb/blob/master/CODE_OF_CONDUCT.md)).
|
|
211
|
+
New behaviour ships with a spec in `spec/lib/` and, if server-quirk-sensitive, a row
|
|
212
|
+
in the Server & version notes table.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# RAG (Retrieval-Augmented Generation) Example
|
|
2
|
+
#
|
|
3
|
+
# This example demonstrates how to use the Arcade::RAGSchema module
|
|
4
|
+
# to create a RAG-compatible document store with vector embeddings.
|
|
5
|
+
#
|
|
6
|
+
# Prerequisites:
|
|
7
|
+
# - A running ArcadeDB server with vector search support
|
|
8
|
+
# - An embedding service (e.g., OpenAI, HuggingFace) to generate embeddings
|
|
9
|
+
#
|
|
10
|
+
# Setup:
|
|
11
|
+
# 1. Create a model that includes Arcade::RAGSchema
|
|
12
|
+
# 2. Call create_type to set up the database schema
|
|
13
|
+
# 3. Insert documents with their embeddings
|
|
14
|
+
# 4. Perform vector or hybrid search
|
|
15
|
+
|
|
16
|
+
require 'bundler/setup'
|
|
17
|
+
require 'arcade'
|
|
18
|
+
|
|
19
|
+
# Example model definition
|
|
20
|
+
module RAG
|
|
21
|
+
class Document < Arcade::Vertex
|
|
22
|
+
include Arcade::RAGSchema
|
|
23
|
+
|
|
24
|
+
attribute :content, Types::String
|
|
25
|
+
attribute :embedding?, Types::Array.of(Types::Float).optional
|
|
26
|
+
attribute :metadata?, Types::Hash.optional
|
|
27
|
+
attribute :source_url?, Types::String.optional
|
|
28
|
+
attribute :created_at?, Types::JSON::DateTime.optional
|
|
29
|
+
|
|
30
|
+
# Override db_init to use RAG schema
|
|
31
|
+
def self.db_init
|
|
32
|
+
rag_db_init(vector_dimension: 1536, full_text_fields: ['content', 'metadata'])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if $0 == __FILE__
|
|
38
|
+
# Connect to database
|
|
39
|
+
Arcade::Init.connect('test')
|
|
40
|
+
|
|
41
|
+
# Create the type with RAG schema
|
|
42
|
+
RAG::Document.create_type
|
|
43
|
+
|
|
44
|
+
# Example: Insert a document with embedding
|
|
45
|
+
# In practice, you would generate the embedding using an external service
|
|
46
|
+
sample_embedding = Array.new(1536) { rand } # Replace with actual embedding
|
|
47
|
+
|
|
48
|
+
doc = RAG::Document.create(
|
|
49
|
+
content: "ArcadeDB is a multi-model graph database built from the ground up.",
|
|
50
|
+
embedding: sample_embedding,
|
|
51
|
+
metadata: { author: "ArcadeDB Team", tags: ["database", "graph"] },
|
|
52
|
+
source_url: "https://arcadedb.com",
|
|
53
|
+
created_at: DateTime.now
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
puts "Created document: #{doc.rid}"
|
|
57
|
+
|
|
58
|
+
# Example: Vector similarity search
|
|
59
|
+
# query_embedding = generate_embedding("What is a graph database?")
|
|
60
|
+
# results = RAG::Document.vector_search(query_embedding, limit: 5, threshold: 0.7)
|
|
61
|
+
# puts "Found #{results.count} similar documents"
|
|
62
|
+
|
|
63
|
+
# Example: Hybrid search (vector + full-text)
|
|
64
|
+
# results = RAG::Document.hybrid_search(
|
|
65
|
+
# query_embedding,
|
|
66
|
+
# "multi-model graph database",
|
|
67
|
+
# limit: 5
|
|
68
|
+
# )
|
|
69
|
+
# puts "Found #{results.count} hybrid search results"
|
|
70
|
+
|
|
71
|
+
puts "RAG schema setup complete!"
|
|
72
|
+
end
|