arcadedb 0.5.0 → 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 +31 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +64 -58
- data/RAG_SCHEMA.md +239 -0
- data/README.md +131 -236
- data/arcade.yml +5 -5
- data/bin/console +1 -0
- data/examples/rag_example.rb +72 -0
- data/examples/rag_usecase_demo.rb +345 -0
- data/lib/arcade/api/operations.rb +33 -24
- data/lib/arcade/api/primitives.rb +52 -2
- data/lib/arcade/base.rb +35 -14
- data/lib/arcade/database.rb +16 -7
- data/lib/arcade/init.rb +5 -2
- data/lib/arcade/match.rb +85 -37
- data/lib/arcade/query.rb +10 -15
- data/lib/arcade/rag_schema.rb +131 -0
- data/lib/arcade/support/model.rb +20 -13
- data/lib/arcade/support/sql.rb +1 -1
- data/lib/arcade/support/string.rb +11 -14
- data/lib/arcade/timeseries.rb +211 -0
- data/lib/arcade/version.rb +1 -1
- data/lib/arcade.rb +7 -4
- data/lib/model/document.rb +1 -1
- data/lib/model/edge.rb +17 -0
- data/lib/model/revision.rb +41 -0
- data/lib/model/revision_record.rb +29 -0
- data/lib/model/timeseries.rb +6 -0
- data/lib/model/vertex.rb +52 -1
- data/lib/models.rb +3 -0
- data/lib/types.rb +35 -0
- metadata +12 -6
data/README.md
CHANGED
|
@@ -1,317 +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
|
|
20
|
+
bundle init && bundle add arcadedb
|
|
21
|
+
mkdir model bin
|
|
22
|
+
# copy bin/console and arcade.yml from this repo, adapt arcade.yml
|
|
25
23
|
```
|
|
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
24
|
|
|
29
|
-
|
|
25
|
+
`arcade.yml` (project root or `config/`):
|
|
30
26
|
|
|
31
|
-
|
|
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
|
|
32
34
|
```
|
|
33
|
-
$ cd bin && ./console.rb t ( or "d" or "p" for Test, Development and Production environment)
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
## Add to a project
|
|
39
|
-
Just require it in your program:
|
|
40
|
-
```ruby
|
|
41
|
-
require "arcade"
|
|
42
|
-
```
|
|
43
|
-
## Config
|
|
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`.
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
## Architecture
|
|
46
40
|
|
|
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+)
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
The `spec`-files in the rspec-test-suite-section are worth reading, too.
|
|
53
|
-
|
|
54
|
-
## Implementation
|
|
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, but have to be declared in the model-file.
|
|
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.create name: 'Hubert', age: '25'
|
|
101
|
-
f = Person.create 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, however the class has changed to `Arcade::Query`.
|
|
135
|
-
|
|
136
|
-
## Match
|
|
137
96
|
|
|
138
|
-
|
|
139
|
-
necessary. With the help of some ruby magic, the creation of match-statements is simply an no-brainer.
|
|
97
|
+
MATCH generator — also from a vertex instance (handy when `params[:rid]` is given):
|
|
140
98
|
|
|
141
99
|
```ruby
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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.
|
|
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)
|
|
149
103
|
```
|
|
150
104
|
|
|
151
|
-
|
|
152
|
-
easily constructed without extensive string-manipulations.
|
|
153
|
-
|
|
154
|
-
The **second Layer** handles Database-Requests.
|
|
155
|
-
In its actual implementation, these requests are delegated to the HTTP/JSON-API.
|
|
156
|
-
|
|
157
|
-
`Arcade::Init` uses the database specification of the `arcade.yml` file.
|
|
158
|
-
The database-handle is always present through `Arcade::Init.db`
|
|
105
|
+
Query objects nest (hybrid queries without string surgery):
|
|
159
106
|
|
|
160
107
|
```ruby
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
$ DB.get nn, mm # returns a Aracde:Base object
|
|
164
|
-
# rid is either "#11:10:" or two numbers
|
|
165
|
-
$ DB.query querystring # returns either an Array of results (as Hash)
|
|
166
|
-
$ DB.execute { querystring } # execute a non idempotent query within a (nested) transaction
|
|
167
|
-
$ DB.create <name>, attribute: value .... # Creates a new <Document | Vertex> and returns the rid
|
|
168
|
-
# Operation is performed as Database-Transaction and is rolled back on error
|
|
169
|
-
$ DB.insert <name>, attribute: value .... # Inserts a new <Document | Vertex> and returns the new object
|
|
170
|
-
$ DB.create_edge <name>, from: <rid> or [rid, rid, ..] , to: <rid> or [rid, rid, ..]
|
|
171
|
-
|
|
108
|
+
Query.new(from: match_object, projection: 'median(note_count)', where: '$depth>=50')
|
|
172
109
|
```
|
|
173
110
|
|
|
174
|
-
|
|
111
|
+
## TimeSeries (new in 0.5.4)
|
|
175
112
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
```ruby
|
|
180
|
-
$ DB.query "select from my_names limit 1"
|
|
181
|
-
# which is identical to
|
|
182
|
-
$ My::Names.query( limit: 1).query
|
|
183
|
-
=> [{:@out=>0, :@rid=>"#225:6", :@in=>0, :@type=>"my_names", :@cat=>"v", :name=>"Zaber"}]
|
|
184
|
-
# then
|
|
185
|
-
$ My::Names.query( limit: 1).query.allocate_model.to_human
|
|
186
|
-
=> ["<my_names[#225:6]: name: Zaber>" ]
|
|
187
|
-
# replaces the hash with a My::Names Object
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
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.
|
|
191
115
|
|
|
192
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
|
|
193
123
|
|
|
194
|
-
|
|
195
|
-
$ Arcade::Api.create_database <a string> # returns true if successfull
|
|
196
|
-
$ Arcade::Api.drop_database <a string> # returns true if successfull
|
|
124
|
+
Ohlcv.create_type
|
|
197
125
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
202
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
|
|
203
134
|
|
|
204
|
-
|
|
205
|
-
|
|
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 }
|
|
206
138
|
```
|
|
207
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.
|
|
208
143
|
|
|
209
|
-
|
|
210
|
-
```ruby
|
|
211
|
-
{ :query => "<the query string> ",
|
|
212
|
-
:language => one of :sql, :cypher, :gmelin: :neo4 ,
|
|
213
|
-
:params => a hash of parameters,
|
|
214
|
-
:limit => a number ,
|
|
215
|
-
:serializer => one of :graph, :record }
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## ORM Behavior
|
|
219
|
-
|
|
220
|
-
Simple tasks are implemented on the model layer.
|
|
144
|
+
## RAG
|
|
221
145
|
|
|
222
|
-
|
|
146
|
+
`require 'arcade/rag_schema'` — vector + full-text schema for retrieval pipelines.
|
|
147
|
+
See `RAG_SCHEMA.md` and `examples/rag_example.rb`.
|
|
223
148
|
|
|
224
|
-
|
|
149
|
+
## Transactions & audit
|
|
225
150
|
|
|
226
151
|
```ruby
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
User.upsert set: { age: 46 }, where: { name: 'Hugo' }
|
|
152
|
+
db = Arcade::Init.db
|
|
153
|
+
db.begin_transaction; ... ; db.commit # or db.rollback
|
|
230
154
|
```
|
|
231
|
-
either creates the record (and returns it) or returns the existing database entry. Obviously, the addressed attribute (where condition)
|
|
232
|
-
must have a proper index.
|
|
233
155
|
|
|
234
|
-
|
|
156
|
+
`Arcade::Revision` subclasses keep an append-only `protocol` stack of
|
|
157
|
+
`Arcade::RevisionRecord` entries; every `update` appends (old value + action + user).
|
|
235
158
|
|
|
236
|
-
|
|
159
|
+
## Low-level access
|
|
237
160
|
|
|
238
|
-
Apart from accessing attributes by their method-name, adjacent edges and notes are fetched through
|
|
239
|
-
|
|
240
|
-
```ruby
|
|
241
|
-
new_vertex = ->(n) { Node.create( note_count: n ) } ## lambda to create a Node type record
|
|
242
|
-
nucleus = BaseNode.create item: 'b' ## create a start node
|
|
243
|
-
(1..10).each{ |n| nucleus.assign( via: Connects, vertex: new_vertex[n]) } ## connect nodes via Connects-Edges
|
|
244
|
-
```
|
|
245
|
-
After creating a star-like structure, the environment can be explored
|
|
246
|
-
```ruby
|
|
247
|
-
nucleus.edges.to_human
|
|
248
|
-
=>["<connects[#80:14] :.: #4:0->{}->#34:2>",
|
|
249
|
-
"<connects[#79:13] :.: #4:0->{}->#31:1>",
|
|
250
|
-
( ... )
|
|
251
|
-
"<connects[#79:14] :.: #4:0->{}->#31:2>"]
|
|
252
|
-
|
|
253
|
-
nucleus.nodes.to_human
|
|
254
|
-
=> ["<node[#34:2]: item: 10>",
|
|
255
|
-
"<node[#31:1]: item: 1>",
|
|
256
|
-
( ... )
|
|
257
|
-
"<node[#31:2]: item: 9>"]
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Edges provide a `vertices`-method to load connected ones. Both vertices and edges expose `in`, `out`, `both`-methods to select connections further.
|
|
261
|
-
Specific edge-classes (types) are provided with the `via:` parameter, as shown in `assign` above.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
### Traverse Facility
|
|
265
|
-
|
|
266
|
-
To ease queries to the graph database, `Arcade::Query` supports traversal of nodes
|
|
267
|
-
|
|
268
|
-
Create a Chain of Nodes:
|
|
269
161
|
```ruby
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
start_node = Arcade::ExtraNode.create( item: 'linear' )
|
|
277
|
-
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
|
|
278
167
|
```
|
|
279
168
|
|
|
280
|
-
|
|
169
|
+
Query payload hash: `{ query:, language: :sql|:cypher|:gremlin|:neo4j, params:, limit:, serializer: :graph|:record }`.
|
|
281
170
|
|
|
282
|
-
|
|
283
|
-
hundred_elements = start_node.traverse :out, via: Arcade::Connects, depth: 100
|
|
284
|
-
median = Query.new from: hundred_elements,
|
|
285
|
-
projection: 'median(note_count)',
|
|
286
|
-
where: '$depth>=50'
|
|
287
|
-
median.to_s
|
|
288
|
-
-=> select median(note_count) from ( traverse out(connects) from #52:0 while $depth < 100 ) where $depth>=50
|
|
289
|
-
=> {:"median(note_count)"=>75.5 }
|
|
290
|
-
```
|
|
171
|
+
## Server & version notes (verified ArcadeDB 26.10)
|
|
291
172
|
|
|
292
|
-
|
|
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 |
|
|
293
183
|
|
|
294
|
-
|
|
184
|
+
## Testing
|
|
295
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
|
|
296
189
|
```
|
|
297
|
-
db = Arcade::Init.db
|
|
298
|
-
db.begin_transaction
|
|
299
|
-
|
|
300
|
-
{ perform insert, update, query, etc tasks in the database }
|
|
301
190
|
|
|
302
|
-
|
|
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.
|
|
303
193
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
db.rollback
|
|
194
|
+
## Repository layout
|
|
307
195
|
|
|
308
196
|
```
|
|
309
|
-
|
|
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
|
|
204
|
+
```
|
|
310
205
|
|
|
311
206
|
## Contributing
|
|
312
207
|
|
|
313
|
-
Bug reports and pull requests are welcome
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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.
|
data/arcade.yml
CHANGED
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
:test:
|
|
4
4
|
dbname: playground
|
|
5
5
|
user: root
|
|
6
|
-
pass:
|
|
6
|
+
pass: topo1focus
|
|
7
7
|
:development:
|
|
8
8
|
dbname: devel
|
|
9
9
|
user: root
|
|
10
|
-
pass:
|
|
10
|
+
pass: topo1focus
|
|
11
11
|
:production:
|
|
12
12
|
dbname: production
|
|
13
13
|
user: root
|
|
14
|
-
pass:
|
|
14
|
+
pass: topo1focus
|
|
15
15
|
:admin:
|
|
16
|
-
:host:
|
|
16
|
+
:host: localhost
|
|
17
17
|
:port: 2480
|
|
18
18
|
:user: root
|
|
19
|
-
:pass:
|
|
19
|
+
:pass: topo1focus
|
|
20
20
|
:logger: stdout # 'file' or 'stdout'
|
|
21
21
|
:namespace: Arcade # Default Namespace
|
|
22
22
|
:autoload: true # load model if a link is detected in a record
|
data/bin/console
CHANGED
|
@@ -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
|