Skip to content

Commit 2debdd9

Browse files
authored
chore: bump deps (#52)
* chore: bump deps * add addititional tests * feat: add JSON path syntax support for PRQL queries PRQL users can now use the same intuitive dot notation for JSON access as SQL users. Paths like status.phase or spec.containers[0].image are automatically converted to s-strings with arrow operators before PRQL compilation. Changes: - Add convert_path_to_arrows() in json_path.rs for string-based path parsing - Add preprocess_prql_json_paths() in prql.rs to convert paths to s-strings - Update preprocess_sql() to call PRQL preprocessor before compilation - Add comprehensive unit tests for the new functionality - Update integration test 23-prql-parity.sh to use dot notation * refactor: simplify PRQL JSON path preprocessing code Extract parsing logic into focused helper functions for clarity: json_path.rs: - consume_identifier() - extracts alphanumeric identifiers - consume_field_name() - extracts field names including hyphens - parse_path_segments() - orchestrates segment parsing - parse_bracket_segment() - handles [n] and [] bracket parsing prql.rs: - Replace boolean flag with cleaner match statement - flush_code_buffer() - handles buffer conversion - copy_string_literal() - encapsulates string handling with escapes - Rename buffer to code_buffer for clarity All 306 tests pass. No functional changes.
1 parent e9c2d51 commit 2debdd9

14 files changed

Lines changed: 1924 additions & 275 deletions

File tree

Cargo.lock

Lines changed: 252 additions & 221 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ categories = ["command-line-utilities", "database"]
1212

1313
[dependencies]
1414
# SQL query engine (Apache Arrow DataFusion)
15-
datafusion = { version = "51", default-features = false, features = [
15+
datafusion = { version = "52.0.0", default-features = false, features = [
1616
"nested_expressions",
1717
"datetime_expressions",
1818
"regex_expressions",
@@ -21,80 +21,76 @@ datafusion = { version = "51", default-features = false, features = [
2121
"sql",
2222
"recursive_protection",
2323
] }
24-
datafusion-functions-json = "0.51"
24+
datafusion-functions-json = "0.52.0"
2525

2626
# PRQL compiler (alternative query language)
2727
prqlc = "0.13"
2828

2929
# Kubernetes client
30-
kube = { version = "2", features = ["runtime", "client", "derive", "rustls-tls", "http-proxy"] }
31-
k8s-openapi = { version = "0.26", features = ["v1_32"] }
32-
k8s-metrics = "0.26"
30+
kube = { version = "3.0.0", features = ["runtime", "client", "derive", "rustls-tls", "http-proxy"] }
31+
k8s-openapi = { version = "0.27.0", features = ["v1_32"] }
32+
k8s-metrics = "0.27.0"
3333

3434
# TLS provider for rustls (aws-lc-rs preferred for performance)
35-
rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs", "std", "tls12"] }
35+
rustls = { version = "0.23.36", default-features = false, features = ["aws-lc-rs", "std", "tls12"] }
3636

3737
# PostgreSQL wire protocol (for daemon mode)
38-
datafusion-postgres = "0.13"
38+
datafusion-postgres = "0.14.0"
3939

4040
# REPL / CLI
41-
rustyline = { version = "17", features = ["derive"] }
42-
indicatif = "0.18"
43-
console = "0.16"
44-
comfy-table = "7"
45-
dirs = "6"
41+
rustyline = { version = "17.0.2", features = ["derive"] }
42+
indicatif = "0.18.3"
43+
console = "0.16.2"
44+
comfy-table = "7.2.2"
45+
dirs = "6.0.0"
4646

4747
# Async runtime
48-
tokio = { version = "1", features = ["full"] }
48+
tokio = { version = "1.49.0", features = ["full"] }
4949

5050
# Random number generation (for retry jitter)
51-
fastrand = "2"
51+
fastrand = "2.3.0"
5252

5353
# CLI argument parsing
54-
clap = { version = "4", features = ["derive"] }
54+
clap = { version = "4.5.54", features = ["derive"] }
5555

5656
# Serialization
57-
serde = { version = "1", features = ["derive"] }
58-
serde_json = "1"
57+
serde = { version = "1.0.228", features = ["derive"] }
58+
serde_json = "1.0.149"
5959

6060
# Date/time
61-
chrono = "0.4"
61+
chrono = "0.4.43"
6262

6363
# Error handling
64-
anyhow = "1"
64+
anyhow = "1.0.100"
6565

6666
# Logging
67-
tracing = "0.1"
68-
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
69-
tracing-rolling-file = { version = "0.1", features = ["non-blocking"] }
67+
tracing = "0.1.44"
68+
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
69+
tracing-rolling-file = { version = "0.1.3", features = ["non-blocking"] }
7070

7171
# Futures
72-
futures = "0.3"
72+
futures = "0.3.31"
7373

7474
# Async streaming
75-
async-stream = "0.3"
75+
async-stream = "0.3.6"
7676

7777
# Async trait support
78-
async-trait = "0.1"
78+
async-trait = "0.1.89"
7979

8080
# YAML output
81-
serde_yaml = "0.9"
81+
serde_yaml = "0.9.34"
8282

8383
# Regex for LIKE pattern matching
84-
regex = "1"
84+
regex = "1.12.2"
8585

86-
# Pin lazy-regex to 3.4.2 (3.5.0 has a bug with regex::bytes)
87-
lazy-regex = "=3.5.1"
86+
lazy-regex = "3.5.1"
8887

8988
# Atomic file operations
90-
tempfile = "3"
91-
92-
[dev-dependencies]
89+
tempfile = "3.24.0"
9390

9491
[profile.release]
9592
strip = true
9693
lto = "thin"
9794
codegen-units = 1
9895
opt-level = "z" # Optimize for size
9996

100-

src/cli/args.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,171 @@ pub enum OutputFormat {
6868
Csv,
6969
Yaml,
7070
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use super::*;
75+
76+
#[test]
77+
fn test_default_output_format() {
78+
let args = Args::parse_from(["k8sql"]);
79+
assert!(matches!(args.output, OutputFormat::Table));
80+
}
81+
82+
#[test]
83+
fn test_output_format_json() {
84+
let args = Args::parse_from(["k8sql", "-o", "json"]);
85+
assert!(matches!(args.output, OutputFormat::Json));
86+
}
87+
88+
#[test]
89+
fn test_output_format_csv() {
90+
let args = Args::parse_from(["k8sql", "-o", "csv"]);
91+
assert!(matches!(args.output, OutputFormat::Csv));
92+
}
93+
94+
#[test]
95+
fn test_output_format_yaml() {
96+
let args = Args::parse_from(["k8sql", "-o", "yaml"]);
97+
assert!(matches!(args.output, OutputFormat::Yaml));
98+
}
99+
100+
#[test]
101+
fn test_context_single() {
102+
let args = Args::parse_from(["k8sql", "-c", "prod"]);
103+
assert_eq!(args.context, Some("prod".to_string()));
104+
}
105+
106+
#[test]
107+
fn test_context_comma_separated() {
108+
let args = Args::parse_from(["k8sql", "-c", "prod,staging,dev"]);
109+
assert_eq!(args.context, Some("prod,staging,dev".to_string()));
110+
}
111+
112+
#[test]
113+
fn test_context_glob_pattern() {
114+
let args = Args::parse_from(["k8sql", "-c", "prod-*"]);
115+
assert_eq!(args.context, Some("prod-*".to_string()));
116+
}
117+
118+
#[test]
119+
fn test_context_wildcard() {
120+
let args = Args::parse_from(["k8sql", "-c", "*"]);
121+
assert_eq!(args.context, Some("*".to_string()));
122+
}
123+
124+
#[test]
125+
fn test_query_simple() {
126+
let args = Args::parse_from(["k8sql", "-q", "SELECT * FROM pods"]);
127+
assert_eq!(args.query, Some("SELECT * FROM pods".to_string()));
128+
}
129+
130+
#[test]
131+
fn test_query_with_special_characters() {
132+
let args = Args::parse_from([
133+
"k8sql",
134+
"-q",
135+
"SELECT * FROM pods WHERE labels->>'app' = 'nginx'",
136+
]);
137+
assert!(args.query.as_ref().unwrap().contains("->>"));
138+
}
139+
140+
#[test]
141+
fn test_file_flag() {
142+
let args = Args::parse_from(["k8sql", "-f", "/path/to/query.sql"]);
143+
assert_eq!(args.file, Some("/path/to/query.sql".to_string()));
144+
}
145+
146+
#[test]
147+
fn test_no_headers_flag() {
148+
let args = Args::parse_from(["k8sql", "--no-headers"]);
149+
assert!(args.no_headers);
150+
}
151+
152+
#[test]
153+
fn test_verbose_flag() {
154+
let args = Args::parse_from(["k8sql", "-v"]);
155+
assert!(args.verbose);
156+
}
157+
158+
#[test]
159+
fn test_refresh_crds_flag() {
160+
let args = Args::parse_from(["k8sql", "--refresh-crds"]);
161+
assert!(args.refresh_crds);
162+
}
163+
164+
#[test]
165+
fn test_daemon_subcommand_defaults() {
166+
let args = Args::parse_from(["k8sql", "daemon"]);
167+
match args.command {
168+
Some(Command::Daemon { port, bind }) => {
169+
assert_eq!(port, 15432);
170+
assert_eq!(bind, "127.0.0.1");
171+
}
172+
_ => panic!("Expected Daemon command"),
173+
}
174+
}
175+
176+
#[test]
177+
fn test_daemon_custom_port() {
178+
let args = Args::parse_from(["k8sql", "daemon", "--port", "5432"]);
179+
match args.command {
180+
Some(Command::Daemon { port, .. }) => assert_eq!(port, 5432),
181+
_ => panic!("Expected Daemon command"),
182+
}
183+
}
184+
185+
#[test]
186+
fn test_daemon_custom_bind() {
187+
let args = Args::parse_from(["k8sql", "daemon", "--bind", "0.0.0.0"]);
188+
match args.command {
189+
Some(Command::Daemon { bind, .. }) => assert_eq!(bind, "0.0.0.0"),
190+
_ => panic!("Expected Daemon command"),
191+
}
192+
}
193+
194+
#[test]
195+
fn test_daemon_both_options() {
196+
let args = Args::parse_from(["k8sql", "daemon", "-p", "5433", "-b", "192.168.1.1"]);
197+
match args.command {
198+
Some(Command::Daemon { port, bind }) => {
199+
assert_eq!(port, 5433);
200+
assert_eq!(bind, "192.168.1.1");
201+
}
202+
_ => panic!("Expected Daemon command"),
203+
}
204+
}
205+
206+
#[test]
207+
fn test_interactive_subcommand() {
208+
let args = Args::parse_from(["k8sql", "interactive"]);
209+
assert!(matches!(args.command, Some(Command::Interactive)));
210+
}
211+
212+
#[test]
213+
fn test_combined_flags() {
214+
let args = Args::parse_from([
215+
"k8sql",
216+
"-c",
217+
"prod",
218+
"-q",
219+
"SELECT name FROM pods",
220+
"-o",
221+
"json",
222+
"-v",
223+
]);
224+
assert_eq!(args.context, Some("prod".to_string()));
225+
assert_eq!(args.query, Some("SELECT name FROM pods".to_string()));
226+
assert!(matches!(args.output, OutputFormat::Json));
227+
assert!(args.verbose);
228+
}
229+
230+
#[test]
231+
fn test_no_command_no_query() {
232+
// Just k8sql with no arguments (interactive mode by default)
233+
let args = Args::parse_from(["k8sql"]);
234+
assert!(args.command.is_none());
235+
assert!(args.query.is_none());
236+
assert!(args.context.is_none());
237+
}
238+
}

src/daemon/pgwire_server.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
use std::sync::Arc;
55

6-
use datafusion_postgres::auth::AuthManager;
76
use datafusion_postgres::datafusion_pg_catalog::pg_catalog::context::EmptyContextProvider;
87
use datafusion_postgres::datafusion_pg_catalog::setup_pg_catalog;
98
use datafusion_postgres::{QueryHook, ServerOptions, serve_with_hooks};
@@ -46,9 +45,6 @@ impl PgWireServer {
4645

4746
let ctx = Arc::new(ctx);
4847

49-
// Create default auth manager (accepts "postgres" user with empty password)
50-
let auth_manager = Arc::new(AuthManager::new());
51-
5248
// Create custom hooks for k8sql-specific commands
5349
let hooks: Vec<Arc<dyn QueryHook>> = vec![
5450
Arc::new(SetConfigHook::new()), // Handle SET commands from PostgreSQL clients
@@ -75,7 +71,7 @@ impl PgWireServer {
7571
);
7672

7773
// Use datafusion-postgres to serve queries with our custom hooks
78-
serve_with_hooks(ctx, &server_options, auth_manager, hooks)
74+
serve_with_hooks(ctx, &server_options, hooks)
7975
.await
8076
.map_err(|e| anyhow::anyhow!("{}", e))
8177
}

src/datafusion_integration/execution.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -512,25 +512,23 @@ mod tests {
512512

513513
#[test]
514514
fn test_is_not_found_error_with_404() {
515-
let api_err = kube::Error::Api(kube::error::ErrorResponse {
516-
status: "Failure".to_string(),
517-
message: "namespaces \"missing\" not found".to_string(),
518-
reason: "NotFound".to_string(),
519-
code: 404,
520-
});
515+
let api_err = kube::Error::Api(
516+
kube::core::Status::failure("namespaces \"missing\" not found", "NotFound")
517+
.with_code(404)
518+
.boxed(),
519+
);
521520
let err = anyhow::Error::new(api_err);
522521

523522
assert!(is_not_found_error(&err));
524523
}
525524

526525
#[test]
527526
fn test_is_not_found_error_with_other_code() {
528-
let api_err = kube::Error::Api(kube::error::ErrorResponse {
529-
status: "Failure".to_string(),
530-
message: "Forbidden".to_string(),
531-
reason: "Forbidden".to_string(),
532-
code: 403,
533-
});
527+
let api_err = kube::Error::Api(
528+
kube::core::Status::failure("Forbidden", "Forbidden")
529+
.with_code(403)
530+
.boxed(),
531+
);
534532
let err = anyhow::Error::new(api_err);
535533

536534
assert!(!is_not_found_error(&err));

0 commit comments

Comments
 (0)