Skip to content

Commit 867279a

Browse files
authored
feat(cubesql): Support startsWith/endsWidth filters (QuickSight) (cube-js#5302)
1 parent 3e563db commit 867279a

File tree

2 files changed

+626
-8
lines changed

2 files changed

+626
-8
lines changed

rust/cubesql/cubesql/src/compile/mod.rs

Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11213,4 +11213,380 @@ ORDER BY \"COUNT(count)\" DESC"
1121311213
)
1121411214
}
1121511215
}
11216+
11217+
#[tokio::test]
11218+
async fn test_quicksight_str_starts_with_query() {
11219+
init_logger();
11220+
11221+
let logical_plan = convert_select_to_query_plan(
11222+
r#"
11223+
SELECT
11224+
"customer_gender" AS "uuid.customer_gender",
11225+
COUNT(*) AS "count",
11226+
DENSE_RANK() OVER (ORDER BY "customer_gender" DESC NULLS LAST) AS "$RANK_1"
11227+
FROM "public"."KibanaSampleDataEcommerce"
11228+
WHERE LEFT("customer_gender", 1) = 'f'
11229+
GROUP BY "customer_gender";
11230+
"#
11231+
.to_string(),
11232+
DatabaseProtocol::PostgreSQL,
11233+
)
11234+
.await
11235+
.as_logical_plan();
11236+
11237+
assert_eq!(
11238+
logical_plan.find_cube_scan().request,
11239+
V1LoadRequestQuery {
11240+
measures: Some(vec!["KibanaSampleDataEcommerce.count".to_string()]),
11241+
dimensions: Some(vec!["KibanaSampleDataEcommerce.customer_gender".to_string()]),
11242+
segments: Some(vec![]),
11243+
time_dimensions: None,
11244+
order: None,
11245+
limit: None,
11246+
offset: None,
11247+
filters: Some(vec![V1LoadRequestQueryFilterItem {
11248+
member: Some("KibanaSampleDataEcommerce.customer_gender".to_string()),
11249+
operator: Some("startsWith".to_string()),
11250+
values: Some(vec!["f".to_string()]),
11251+
or: None,
11252+
and: None,
11253+
}]),
11254+
}
11255+
)
11256+
}
11257+
11258+
#[tokio::test]
11259+
async fn test_quicksight_str_ends_with_query() {
11260+
init_logger();
11261+
11262+
let logical_plan = convert_select_to_query_plan(
11263+
r#"
11264+
SELECT
11265+
"customer_gender" AS "uuid.customer_gender",
11266+
COUNT(*) AS "count",
11267+
DENSE_RANK() OVER (ORDER BY "customer_gender" DESC NULLS LAST) AS "$RANK_1"
11268+
FROM "public"."KibanaSampleDataEcommerce"
11269+
WHERE RIGHT("customer_gender", 2) = 'le'
11270+
GROUP BY "customer_gender";
11271+
"#
11272+
.to_string(),
11273+
DatabaseProtocol::PostgreSQL,
11274+
)
11275+
.await
11276+
.as_logical_plan();
11277+
11278+
assert_eq!(
11279+
logical_plan.find_cube_scan().request,
11280+
V1LoadRequestQuery {
11281+
measures: Some(vec!["KibanaSampleDataEcommerce.count".to_string()]),
11282+
dimensions: Some(vec!["KibanaSampleDataEcommerce.customer_gender".to_string()]),
11283+
segments: Some(vec![]),
11284+
time_dimensions: None,
11285+
order: None,
11286+
limit: None,
11287+
offset: None,
11288+
filters: Some(vec![V1LoadRequestQueryFilterItem {
11289+
member: Some("KibanaSampleDataEcommerce.customer_gender".to_string()),
11290+
operator: Some("endsWith".to_string()),
11291+
values: Some(vec!["le".to_string()]),
11292+
or: None,
11293+
and: None,
11294+
}]),
11295+
}
11296+
)
11297+
}
11298+
11299+
#[tokio::test]
11300+
async fn test_quicksight_str_contains_query() {
11301+
init_logger();
11302+
11303+
let logical_plan = convert_select_to_query_plan(
11304+
r#"
11305+
SELECT
11306+
"customer_gender" AS "uuid.customer_gender",
11307+
COUNT(*) AS "count",
11308+
DENSE_RANK() OVER (ORDER BY "customer_gender" DESC NULLS LAST) AS "$RANK_1"
11309+
FROM "public"."KibanaSampleDataEcommerce"
11310+
WHERE case
11311+
when strpos(substring("customer_gender", 1), 'al') > 0
11312+
then strpos(substring("customer_gender", 1), 'al') + 1 - 1
11313+
else 0
11314+
end > 0
11315+
GROUP BY "customer_gender";
11316+
"#
11317+
.to_string(),
11318+
DatabaseProtocol::PostgreSQL,
11319+
)
11320+
.await
11321+
.as_logical_plan();
11322+
11323+
assert_eq!(
11324+
logical_plan.find_cube_scan().request,
11325+
V1LoadRequestQuery {
11326+
measures: Some(vec!["KibanaSampleDataEcommerce.count".to_string()]),
11327+
dimensions: Some(vec!["KibanaSampleDataEcommerce.customer_gender".to_string()]),
11328+
segments: Some(vec![]),
11329+
time_dimensions: None,
11330+
order: None,
11331+
limit: None,
11332+
offset: None,
11333+
filters: Some(vec![V1LoadRequestQueryFilterItem {
11334+
member: Some("KibanaSampleDataEcommerce.customer_gender".to_string()),
11335+
operator: Some("contains".to_string()),
11336+
values: Some(vec!["al".to_string()]),
11337+
or: None,
11338+
and: None,
11339+
}]),
11340+
}
11341+
)
11342+
}
11343+
11344+
#[tokio::test]
11345+
async fn test_quicksight_str_does_not_contain_query() {
11346+
init_logger();
11347+
11348+
let logical_plan = convert_select_to_query_plan(
11349+
r#"
11350+
SELECT
11351+
"customer_gender" AS "uuid.customer_gender",
11352+
COUNT(*) AS "count",
11353+
DENSE_RANK() OVER (ORDER BY "customer_gender" DESC NULLS LAST) AS "$RANK_1"
11354+
FROM "public"."KibanaSampleDataEcommerce"
11355+
WHERE
11356+
case
11357+
when strpos(substring("customer_gender", 1), 'al') > 0
11358+
then strpos(substring("customer_gender", 1), 'al') + 1 - 1 else 0
11359+
end = 0 AND
11360+
"customer_gender" IS NOT NULL
11361+
GROUP BY "customer_gender";
11362+
"#
11363+
.to_string(),
11364+
DatabaseProtocol::PostgreSQL,
11365+
)
11366+
.await
11367+
.as_logical_plan();
11368+
11369+
assert_eq!(
11370+
logical_plan.find_cube_scan().request,
11371+
V1LoadRequestQuery {
11372+
measures: Some(vec!["KibanaSampleDataEcommerce.count".to_string()]),
11373+
dimensions: Some(vec!["KibanaSampleDataEcommerce.customer_gender".to_string()]),
11374+
segments: Some(vec![]),
11375+
time_dimensions: None,
11376+
order: None,
11377+
limit: None,
11378+
offset: None,
11379+
filters: Some(vec![
11380+
V1LoadRequestQueryFilterItem {
11381+
member: Some("KibanaSampleDataEcommerce.customer_gender".to_string()),
11382+
operator: Some("notContains".to_string()),
11383+
values: Some(vec!["al".to_string()]),
11384+
or: None,
11385+
and: None,
11386+
},
11387+
V1LoadRequestQueryFilterItem {
11388+
member: Some("KibanaSampleDataEcommerce.customer_gender".to_string()),
11389+
operator: Some("set".to_string()),
11390+
values: None,
11391+
or: None,
11392+
and: None,
11393+
},
11394+
]),
11395+
}
11396+
)
11397+
}
11398+
11399+
#[tokio::test]
11400+
async fn test_quicksight_num_starts_with_query() {
11401+
init_logger();
11402+
11403+
let logical_plan = convert_select_to_query_plan(
11404+
r#"
11405+
SELECT
11406+
"maxPrice" AS "uuid.maxPrice",
11407+
COUNT(*) AS "count",
11408+
DENSE_RANK() OVER (ORDER BY "maxPrice" DESC NULLS LAST) AS "$RANK_1"
11409+
FROM "public"."KibanaSampleDataEcommerce"
11410+
WHERE LEFT(CAST("maxPrice" AS VARCHAR), 1) = '1'
11411+
GROUP BY "maxPrice";
11412+
"#
11413+
.to_string(),
11414+
DatabaseProtocol::PostgreSQL,
11415+
)
11416+
.await
11417+
.as_logical_plan();
11418+
11419+
assert_eq!(
11420+
logical_plan.find_cube_scan().request,
11421+
V1LoadRequestQuery {
11422+
measures: Some(vec![
11423+
"KibanaSampleDataEcommerce.maxPrice".to_string(),
11424+
"KibanaSampleDataEcommerce.count".to_string(),
11425+
]),
11426+
dimensions: Some(vec![]),
11427+
segments: Some(vec![]),
11428+
time_dimensions: None,
11429+
order: None,
11430+
limit: None,
11431+
offset: None,
11432+
filters: Some(vec![V1LoadRequestQueryFilterItem {
11433+
member: Some("KibanaSampleDataEcommerce.maxPrice".to_string()),
11434+
operator: Some("startsWith".to_string()),
11435+
values: Some(vec!["1".to_string()]),
11436+
or: None,
11437+
and: None,
11438+
}]),
11439+
}
11440+
)
11441+
}
11442+
11443+
#[tokio::test]
11444+
async fn test_quicksight_num_ends_with_query() {
11445+
init_logger();
11446+
11447+
let logical_plan = convert_select_to_query_plan(
11448+
r#"
11449+
SELECT
11450+
"maxPrice" AS "uuid.maxPrice",
11451+
COUNT(*) AS "count",
11452+
DENSE_RANK() OVER (ORDER BY "maxPrice" DESC NULLS LAST) AS "$RANK_1"
11453+
FROM "public"."KibanaSampleDataEcommerce"
11454+
WHERE RIGHT(CAST("maxPrice" AS VARCHAR), 2) = '23'
11455+
GROUP BY "maxPrice";
11456+
"#
11457+
.to_string(),
11458+
DatabaseProtocol::PostgreSQL,
11459+
)
11460+
.await
11461+
.as_logical_plan();
11462+
11463+
assert_eq!(
11464+
logical_plan.find_cube_scan().request,
11465+
V1LoadRequestQuery {
11466+
measures: Some(vec![
11467+
"KibanaSampleDataEcommerce.maxPrice".to_string(),
11468+
"KibanaSampleDataEcommerce.count".to_string(),
11469+
]),
11470+
dimensions: Some(vec![]),
11471+
segments: Some(vec![]),
11472+
time_dimensions: None,
11473+
order: None,
11474+
limit: None,
11475+
offset: None,
11476+
filters: Some(vec![V1LoadRequestQueryFilterItem {
11477+
member: Some("KibanaSampleDataEcommerce.maxPrice".to_string()),
11478+
operator: Some("endsWith".to_string()),
11479+
values: Some(vec!["23".to_string()]),
11480+
or: None,
11481+
and: None,
11482+
}]),
11483+
}
11484+
)
11485+
}
11486+
11487+
#[tokio::test]
11488+
async fn test_quicksight_num_contains_query() {
11489+
init_logger();
11490+
11491+
let logical_plan = convert_select_to_query_plan(
11492+
r#"
11493+
SELECT
11494+
"maxPrice" AS "uuid.maxPrice",
11495+
COUNT(*) AS "count",
11496+
DENSE_RANK() OVER (ORDER BY "maxPrice" DESC NULLS LAST) AS "$RANK_1"
11497+
FROM "public"."KibanaSampleDataEcommerce"
11498+
WHERE case
11499+
when strpos(substring(CAST("maxPrice" AS VARCHAR), 1), '45') > 0
11500+
then strpos(substring(CAST("maxPrice" AS VARCHAR), 1), '45') + 1 - 1
11501+
else 0
11502+
end > 0
11503+
GROUP BY "maxPrice";
11504+
"#
11505+
.to_string(),
11506+
DatabaseProtocol::PostgreSQL,
11507+
)
11508+
.await
11509+
.as_logical_plan();
11510+
11511+
assert_eq!(
11512+
logical_plan.find_cube_scan().request,
11513+
V1LoadRequestQuery {
11514+
measures: Some(vec![
11515+
"KibanaSampleDataEcommerce.maxPrice".to_string(),
11516+
"KibanaSampleDataEcommerce.count".to_string(),
11517+
]),
11518+
dimensions: Some(vec![]),
11519+
segments: Some(vec![]),
11520+
time_dimensions: None,
11521+
order: None,
11522+
limit: None,
11523+
offset: None,
11524+
filters: Some(vec![V1LoadRequestQueryFilterItem {
11525+
member: Some("KibanaSampleDataEcommerce.maxPrice".to_string()),
11526+
operator: Some("contains".to_string()),
11527+
values: Some(vec!["45".to_string()]),
11528+
or: None,
11529+
and: None,
11530+
}]),
11531+
}
11532+
)
11533+
}
11534+
11535+
#[tokio::test]
11536+
async fn test_quicksight_num_does_not_contain_query() {
11537+
init_logger();
11538+
11539+
let logical_plan = convert_select_to_query_plan(
11540+
r#"
11541+
SELECT
11542+
"maxPrice" AS "uuid.maxPrice",
11543+
COUNT(*) AS "count",
11544+
DENSE_RANK() OVER (ORDER BY "maxPrice" DESC NULLS LAST) AS "$RANK_1"
11545+
FROM "public"."KibanaSampleDataEcommerce"
11546+
WHERE
11547+
case
11548+
when strpos(substring(CAST("maxPrice" AS VARCHAR), 1), '67') > 0
11549+
then strpos(substring(CAST("maxPrice" AS VARCHAR), 1), '67') + 1 - 1 else 0
11550+
end = 0 AND
11551+
"maxPrice" IS NOT NULL
11552+
GROUP BY "maxPrice";
11553+
"#
11554+
.to_string(),
11555+
DatabaseProtocol::PostgreSQL,
11556+
)
11557+
.await
11558+
.as_logical_plan();
11559+
11560+
assert_eq!(
11561+
logical_plan.find_cube_scan().request,
11562+
V1LoadRequestQuery {
11563+
measures: Some(vec![
11564+
"KibanaSampleDataEcommerce.maxPrice".to_string(),
11565+
"KibanaSampleDataEcommerce.count".to_string(),
11566+
]),
11567+
dimensions: Some(vec![]),
11568+
segments: Some(vec![]),
11569+
time_dimensions: None,
11570+
order: None,
11571+
limit: None,
11572+
offset: None,
11573+
filters: Some(vec![
11574+
V1LoadRequestQueryFilterItem {
11575+
member: Some("KibanaSampleDataEcommerce.maxPrice".to_string()),
11576+
operator: Some("notContains".to_string()),
11577+
values: Some(vec!["67".to_string()]),
11578+
or: None,
11579+
and: None,
11580+
},
11581+
V1LoadRequestQueryFilterItem {
11582+
member: Some("KibanaSampleDataEcommerce.maxPrice".to_string()),
11583+
operator: Some("set".to_string()),
11584+
values: None,
11585+
or: None,
11586+
and: None,
11587+
},
11588+
]),
11589+
}
11590+
)
11591+
}
1121611592
}

0 commit comments

Comments
 (0)