Skip to content

Commit 6e7930d

Browse files
authored
feat(schema): fetch schema information (#33)
Signed-off-by: Felipi Lima Matozinho <matozinhof.academico@gmail.com>
1 parent 9831aa1 commit 6e7930d

File tree

8 files changed

+287
-7
lines changed

8 files changed

+287
-7
lines changed

examples/basic.mts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Cluster, } from "../index.js"
1+
import { Cluster } from "../index.js"
22

33
const nodes = process.env.CLUSTER_NODES?.split(",") ?? ["127.0.0.1:9042"];
44

@@ -34,4 +34,4 @@ console.log(`Iter queries requested: ${metrics.getQueriesIterNum()}`);
3434
console.log(`Errors occurred: ${metrics.getErrorsNum()}`);
3535
console.log(`Iter errors occurred: ${metrics.getErrorsIterNum()}`);
3636
console.log(`Average latency: ${metrics.getLatencyAvgMs()}`);
37-
console.log(`99.9 latency percentile: ${metrics.getLatencyPercentileMs(99.9)}`);
37+
console.log(`99.9 latency percentile: ${metrics.getLatencyPercentileMs(99.9)}`);

examples/fetch-schema.mts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Cluster } from "../index.js";
2+
3+
const nodes = process.env.CLUSTER_NODES?.split(",") ?? ["127.0.0.1:9042"];
4+
5+
console.log(`Connecting to ${nodes}`);
6+
7+
const cluster = new Cluster({ nodes });
8+
const session = await cluster.connect();
9+
10+
const clusterData = await session.getClusterData();
11+
const keyspaceInfo = clusterData.getKeyspaceInfo();
12+
13+
if (!keyspaceInfo) throw new Error("No data found");
14+
15+
console.log("ALL KEYSPACES");
16+
for (const keyspaceName in keyspaceInfo) {
17+
console.log("========================================================");
18+
const keyspaceData = keyspaceInfo[keyspaceName];
19+
console.log("Keyspace: ", keyspaceName);
20+
console.log(
21+
"replication strategy: ",
22+
keyspaceData.strategy.kind,
23+
keyspaceData.strategy.data,
24+
);
25+
for (const tableName in keyspaceData.tables) {
26+
console.log("-----------------------");
27+
const tableData = keyspaceData.tables[tableName];
28+
console.log("Table: ", tableName);
29+
console.log("partitionKey: ", tableData.partitionKey);
30+
console.log("clusteringKey: ", tableData.clusteringKey);
31+
console.log("columns: ", tableData.columns);
32+
console.log("-----------------------");
33+
}
34+
console.log("========================================================");
35+
}
36+
37+
console.log("================== SPECIFIC KEYSPACES ==================");
38+
console.log(
39+
"keyspace: system_auth | strategy: ",
40+
keyspaceInfo.system_auth.strategy,
41+
);
42+
console.log(
43+
"keyspace: system_traces | strategy: ",
44+
keyspaceInfo.system_traces.strategy,
45+
);
46+
console.log(
47+
"keyspace: system_distributed_everywhere | strategy: ",
48+
keyspaceInfo.system_distributed_everywhere.strategy,
49+
);
50+
console.log(
51+
"keyspace: system_distributed | strategy: ",
52+
keyspaceInfo.system_distributed.strategy,
53+
);

index.d.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,35 @@ export const enum VerifyMode {
5959
None = 0,
6060
Peer = 1
6161
}
62+
export interface ScyllaKeyspace {
63+
strategy: ScyllaStrategy
64+
tables: Record<string, ScyllaTable>
65+
views: Record<string, ScyllaMaterializedView>
66+
}
67+
export interface ScyllaStrategy {
68+
kind: string
69+
data?: SimpleStrategy | NetworkTopologyStrategy | Other
70+
}
71+
export interface SimpleStrategy {
72+
replicationFactor: number
73+
}
74+
export interface NetworkTopologyStrategy {
75+
datacenterRepfactors: Record<string, number>
76+
}
77+
export interface Other {
78+
name: string
79+
data: Record<string, string>
80+
}
81+
export interface ScyllaTable {
82+
columns: Array<string>
83+
partitionKey: Array<string>
84+
clusteringKey: Array<string>
85+
partitioner?: string
86+
}
87+
export interface ScyllaMaterializedView {
88+
viewMetadata: ScyllaTable
89+
baseTableName: string
90+
}
6291
export type ScyllaCluster = Cluster
6392
export class Cluster {
6493
/**
@@ -121,8 +150,9 @@ export class Metrics {
121150
}
122151
export class ScyllaSession {
123152
metrics(): Metrics
124-
execute<T = unknown>(query: string | Query | PreparedStatement, parameters?: Array<number | string | Uuid> | undefined | null): Promise<T>
125-
query<T = unknown>(scyllaQuery: Query, parameters?: Array<number | string | Uuid> | undefined | null): Promise<T>
153+
getClusterData(): Promise<ScyllaClusterData>
154+
execute(query: string | Query | PreparedStatement, parameters?: Array<number | string | Uuid> | undefined | null): Promise<any>
155+
query(scyllaQuery: Query, parameters?: Array<number | string | Uuid> | undefined | null): Promise<any>
126156
prepare(query: string): Promise<PreparedStatement>
127157
/**
128158
* Perform a batch query\
@@ -161,7 +191,7 @@ export class ScyllaSession {
161191
* console.log(await session.execute("SELECT * FROM users"));
162192
* ```
163193
*/
164-
batch<T = unknown>(batch: BatchStatement, parameters: Array<Array<number | string | Uuid> | undefined | null>): Promise<T>
194+
batch(batch: BatchStatement, parameters: Array<Array<number | string | Uuid> | undefined | null>): Promise<any>
165195
/**
166196
* Sends `USE <keyspace_name>` request on all connections\
167197
* This allows to write `SELECT * FROM table` instead of `SELECT * FROM keyspace.table`\
@@ -231,6 +261,13 @@ export class ScyllaSession {
231261
awaitSchemaAgreement(): Promise<Uuid>
232262
checkSchemaAgreement(): Promise<boolean>
233263
}
264+
export class ScyllaClusterData {
265+
/**
266+
* Access keyspaces details collected by the driver Driver collects various schema details like
267+
* tables, partitioners, columns, types. They can be read using this method
268+
*/
269+
getKeyspaceInfo(): Record<string, ScyllaKeyspace> | null
270+
}
234271
export class Uuid {
235272
/** Generates a random UUID v4. */
236273
static randomV4(): Uuid

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ if (!nativeBinding) {
310310
throw new Error(`Failed to load native binding`)
311311
}
312312

313-
const { Compression, Consistency, SerialConsistency, Cluster, VerifyMode, BatchStatement, PreparedStatement, Query, Metrics, ScyllaSession, Uuid } = nativeBinding
313+
const { Compression, Consistency, SerialConsistency, Cluster, VerifyMode, BatchStatement, PreparedStatement, Query, Metrics, ScyllaSession, ScyllaClusterData, Uuid } = nativeBinding
314314

315315
module.exports.Compression = Compression
316316
module.exports.Consistency = Consistency
@@ -322,6 +322,7 @@ module.exports.PreparedStatement = PreparedStatement
322322
module.exports.Query = Query
323323
module.exports.Metrics = Metrics
324324
module.exports.ScyllaSession = ScyllaSession
325+
module.exports.ScyllaClusterData = ScyllaClusterData
325326
module.exports.Uuid = Uuid
326327

327328
const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom')

src/cluster/execution_profile/consistency.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,3 @@ impl From<scylla::statement::Consistency> for Consistency {
5151
}
5252
}
5353
}
54-

src/session/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod metrics;
22
pub mod scylla_session;
3+
pub mod topology;

src/session/scylla_session.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::types::uuid::Uuid;
77
use napi::bindgen_prelude::Either3;
88

99
use super::metrics;
10+
use super::topology::ScyllaClusterData;
1011

1112
#[napi]
1213
pub struct ScyllaSession {
@@ -24,6 +25,18 @@ impl ScyllaSession {
2425
metrics::Metrics::new(self.session.get_metrics())
2526
}
2627

28+
#[napi]
29+
pub async fn get_cluster_data(&self) -> ScyllaClusterData {
30+
self
31+
.session
32+
.refresh_metadata()
33+
.await
34+
.expect("Failed to refresh metadata");
35+
36+
let cluster_data = self.session.get_cluster_data();
37+
cluster_data.into()
38+
}
39+
2740
#[napi]
2841
pub async fn execute(
2942
&self,

src/session/topology.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
use std::collections::HashMap;
2+
use std::sync::Arc;
3+
4+
use napi::bindgen_prelude::Either3;
5+
use scylla::transport::topology::{Keyspace, MaterializedView, Strategy, Table};
6+
use scylla::transport::ClusterData;
7+
8+
// ============= ClusterData ============= //
9+
#[napi]
10+
pub struct ScyllaClusterData {
11+
inner: Arc<ClusterData>,
12+
}
13+
14+
impl From<Arc<ClusterData>> for ScyllaClusterData {
15+
fn from(cluster_data: Arc<ClusterData>) -> Self {
16+
ScyllaClusterData {
17+
inner: cluster_data,
18+
}
19+
}
20+
}
21+
22+
#[napi]
23+
impl ScyllaClusterData {
24+
#[napi]
25+
/// Access keyspaces details collected by the driver Driver collects various schema details like
26+
/// tables, partitioners, columns, types. They can be read using this method
27+
pub fn get_keyspace_info(&self) -> Option<HashMap<String, ScyllaKeyspace>> {
28+
let keyspaces_info = self.inner.get_keyspace_info();
29+
30+
if keyspaces_info.is_empty() {
31+
None
32+
} else {
33+
Some(
34+
keyspaces_info
35+
.iter()
36+
.map(|(k, v)| (k.clone(), ScyllaKeyspace::from((*v).clone())))
37+
.collect(),
38+
)
39+
}
40+
}
41+
}
42+
// ======================================= //
43+
44+
// ============= Keyspace ============= //
45+
#[napi(object)]
46+
#[derive(Clone)]
47+
pub struct ScyllaKeyspace {
48+
pub strategy: ScyllaStrategy,
49+
pub tables: HashMap<String, ScyllaTable>,
50+
pub views: HashMap<String, ScyllaMaterializedView>,
51+
// pub user_defined_types: HashMap<String, ScyllaUserDefinedType>,
52+
}
53+
54+
impl From<Keyspace> for ScyllaKeyspace {
55+
fn from(keyspace: Keyspace) -> Self {
56+
ScyllaKeyspace {
57+
tables: keyspace
58+
.tables
59+
.into_iter()
60+
.map(|(k, v)| (k, ScyllaTable::from(v)))
61+
.collect(),
62+
views: keyspace
63+
.views
64+
.into_iter()
65+
.map(|(k, v)| (k, ScyllaMaterializedView::from(v)))
66+
.collect(),
67+
strategy: keyspace.strategy.into(),
68+
// TODO: Implement ScyllaUserDefinedType
69+
// user_defined_types: keyspace.user_defined_types.into_iter().map(|(k, v)| (k, ScyllaUserDefinedType::from(v))).collect(),
70+
}
71+
}
72+
}
73+
// ======================================= //
74+
75+
// ============= Strategy ============= //
76+
#[napi(object)]
77+
#[derive(Clone)]
78+
pub struct ScyllaStrategy {
79+
pub kind: String,
80+
pub data: Option<Either3<SimpleStrategy, NetworkTopologyStrategy, Other>>,
81+
}
82+
83+
#[napi(object)]
84+
#[derive(Clone)]
85+
pub struct SimpleStrategy {
86+
pub replication_factor: u32,
87+
}
88+
89+
#[napi(object)]
90+
#[derive(Clone)]
91+
pub struct NetworkTopologyStrategy {
92+
pub datacenter_repfactors: HashMap<String, i32>,
93+
}
94+
95+
#[napi(object)]
96+
#[derive(Clone)]
97+
pub struct Other {
98+
pub name: String,
99+
pub data: HashMap<String, String>,
100+
}
101+
102+
impl From<Strategy> for ScyllaStrategy {
103+
fn from(strategy: Strategy) -> Self {
104+
match strategy {
105+
Strategy::SimpleStrategy { replication_factor } => ScyllaStrategy {
106+
kind: "SimpleStrategy".to_string(),
107+
data: Some(Either3::A(SimpleStrategy {
108+
replication_factor: replication_factor as u32,
109+
})),
110+
},
111+
Strategy::NetworkTopologyStrategy {
112+
datacenter_repfactors,
113+
} => ScyllaStrategy {
114+
kind: "NetworkTopologyStrategy".to_string(),
115+
data: Some(Either3::B(NetworkTopologyStrategy {
116+
datacenter_repfactors: datacenter_repfactors
117+
.into_iter()
118+
.map(|(k, v)| (k, v as i32))
119+
.collect(),
120+
})),
121+
},
122+
Strategy::Other { name, data } => ScyllaStrategy {
123+
kind: name.clone(),
124+
data: Some(Either3::C(Other {
125+
name: name.clone(),
126+
data,
127+
})),
128+
},
129+
Strategy::LocalStrategy => ScyllaStrategy {
130+
kind: "LocalStrategy".to_string(),
131+
data: None,
132+
},
133+
}
134+
}
135+
}
136+
// ======================================= //
137+
138+
// ============= Table ============= //
139+
#[napi(object)]
140+
#[derive(Clone)]
141+
pub struct ScyllaTable {
142+
pub columns: Vec<String>,
143+
pub partition_key: Vec<String>,
144+
pub clustering_key: Vec<String>,
145+
pub partitioner: Option<String>,
146+
}
147+
148+
impl From<Table> for ScyllaTable {
149+
fn from(table: Table) -> Self {
150+
ScyllaTable {
151+
columns: table.columns.clone().into_keys().collect::<Vec<String>>(),
152+
partition_key: table.partition_key.clone(),
153+
clustering_key: table.clustering_key.clone(),
154+
partitioner: table.partitioner.clone(),
155+
}
156+
}
157+
}
158+
// ======================================= //
159+
160+
// ============= MaterializedView ============= //
161+
#[napi(object)]
162+
#[derive(Clone)]
163+
pub struct ScyllaMaterializedView {
164+
pub view_metadata: ScyllaTable,
165+
pub base_table_name: String,
166+
}
167+
168+
impl From<MaterializedView> for ScyllaMaterializedView {
169+
fn from(view: MaterializedView) -> Self {
170+
ScyllaMaterializedView {
171+
view_metadata: ScyllaTable::from(view.view_metadata),
172+
base_table_name: view.base_table_name,
173+
}
174+
}
175+
}
176+
// ======================================= //

0 commit comments

Comments
 (0)