Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(#4462) Postgres compatibility tests using sqllogictest #4834

Merged
merged 21 commits into from
Jan 21, 2023

Conversation

melgenek
Copy link
Contributor

@melgenek melgenek commented Jan 6, 2023

Which issue does this PR close?

Closes #4462.

Rationale for this change

It is an example implementation of the sqllogictest tests that run on both Postgres and Datafusion and compare results.

What changes are included in this PR?

  • introduce an implementation of a Postgres runner for sqllogictest
  • rewrite integration-tests
    to sqllogictestformat
  • run a subset of tests with both Datafusion and Postgres to verify that both produce the same results.

Are these changes tested?

There is a dedicated Github action that runs Postgres sqllogictest tests.

Are there any user-facing changes?

N/A

@github-actions github-actions bot added core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) labels Jan 6, 2023
datafusion/core/Cargo.toml Show resolved Hide resolved
)
}

pub async fn connect_with_retry(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my implementation a docker container starts before each test. And sometimes first connections fail. I didn't find a solution to deterministically define when a container is fully ready, so I introduced a connection retry.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

I think in general the approach of orchestrating the docker containers using github CI worked well rather than restarting the containers within the tests

datafusion/core/tests/sqllogictests/src/main.rs Outdated Show resolved Hide resolved
@@ -111,23 +111,23 @@ fn register_median_test_tables(ctx: &SessionContext) {
}
}

async fn register_aggregate_csv_by_sql(ctx: &SessionContext) {
pub async fn register_aggregate_csv_by_sql(ctx: &SessionContext) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-used the existing table creation function and updated the data types according to the sql from integration-tests https://github.com/apache/arrow-datafusion/blob/master/integration-tests/create_test_table.sql.

I can, of course, introduce another function.

Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @melgenek -- this is great progress

Comment on lines 113 to 122
half = "2.2.1"
parquet-test-utils = { path = "../../parquet-test-utils" }
postgres-types = { version = "0.2.4", features = ["derive", "with-chrono-0_4"] }
rstest = "0.16.0"
rust_decimal = { version = "1.27.0", features = ["tokio-pg"] }
sqllogictest = "0.10.0"
test-utils = { path = "../../test-utils" }
testcontainers = "0.14.0"
thiserror = "1.0.37"

tokio-postgres = "0.7.7"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are the reasons for these dependencies:

  • half - f16 type for Datafusion
  • testcontainers - creates a fresh docker container with Postgres for each sqllogictest file.
  • postgres-types and tokio-postgres - these are required for writing a Postgres client
  • rust_decimal - converts Postgres "numeric" type to a rust type
  • bigdecimal - provides a common type to do floating number rounding. rust_decimal, unfortunately, doesn't handle numbers of arbitrary precision. For example, rust_decimal could not parse 26156334342021890000000000000000000000 that is currently present in one of .slt tests in Datafusion.

datafusion/core/Cargo.toml Show resolved Hide resolved
}

pub fn big_decimal_to_str(value: BigDecimal) -> String {
value.round(12).normalized().to_string()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All numbers are rounded to 12 decimal digits. Without explicit types Postgres and Datafusion can choose different underlying types. For example, Postgres could choose to use numeric when Datafusion uses int. In order to compare results, all floating number types are converted to the same number of decimal points.

12 is chosen to pass the existing set of tests. I think it could produce errors, for example, when rounding f16 to 12 digits. I would probably use 3 (or 4) decimal digits if high precision is not required for Postgres compatibility tests. 3 or 4 is an expected number of digits for 16 bit binary according to IEEE_754, so it should be safer to round to the smallest possible data type.

if !col.is_valid(row) {
// represent any null value with the string "NULL"
Ok(NULL_STR.to_string())
} else if is_pg_compatibility_test {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a test is a Postgres compatibility test then some conversions apply. The same conversions are used in the Postgres client implementation.

This is needed, because existing sqllogictests use higher precision of floating number results, which would produce different results in Postgres.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I am not sure the super high precision floating point results are necessary. I wonder if we could simply use the lower precision normalization in all the tests (both in pgcompat and non pg compat) 🤔

}};
}

fn cell_to_string(row: &Row, column: &Column, idx: usize) -> String {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation of Postgres client is based on the "postgres-extended" from the sqllogictest-rs repo https://github.com/risinglightdb/sqllogictest-rs/blob/2fb7e36e1857fd6b7949956b496c26ddc463f858/sqllogictest-bin/src/engines/postgres_extended.rs.

All the type conversions are now through rust like in the Datafusion. So every type has to be converted to a string explicitly.

I implemented the types marked as implemented from https://github.com/apache/arrow-datafusion/blob/master/docs/source/user-guide/sql/data_types.md and made their text representation compatible with Datafusion.
I haven't implemented arrays.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +63 to +68
} else if options.postgres_runner {
if is_pg_compatibility_test {
run_test_file_with_postgres(&path, file_name).await?;
} else {
debug!("Skipping test file {:?}", path);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests which name starts with pg_compat_ would run with Datafusion during cargo test.
In order to check compatibility with Postgres, there is a github job that runs these tests with a Postgres runner.

Thus Datafusion runs all the time. And Postgres runs only explicitly. This way cargo test is not affected by the speed of running docker containers and executing Postgres queries.

@melgenek
Copy link
Contributor Author

@alamb The pr is updated according to your previous review:

  • results are defined explicitly
  • Postgres runs only a subset of tests file prefixed with pg_compat_
  • Postgres runs only when the PG_COMPAT env var is set
  • Postgres client is fully rewritten to explicitly convert results according to their types. Datafusion client is updated to have the same conversions. This way text representations are comparable.

@melgenek melgenek marked this pull request as ready for review January 18, 2023 22:05
@melgenek melgenek requested a review from alamb January 18, 2023 22:05
@alamb
Copy link
Contributor

alamb commented Jan 19, 2023

Thanks @melgenek -- this PR is on my list to review -- but I ran out of time today. I will plan to do so tomorrow

Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this @melgenek -- Thank you, I have several changes I would like to make, but I also think we could make them as (fast) follow on PRs. Specifically:

  1. Don't orchestrate the postgres containers with rust test code, rather orchestrate outside (either with an existing container or github CI, and document what is needed). Making this change will avoid requiring docker to run the datafusion tests,
  2. Use the same normalization in postgres and non-postgres code (maybe by decreasing fidelity of datafusion test)
  3. Remove existing postgres python integration tests

Given how large this PR is already I would be inclined to merge it as is and iterate as a follow on. What do you think @xudong963 @jimexist @avantgardnerio ?

if !col.is_valid(row) {
// represent any null value with the string "NULL"
Ok(NULL_STR.to_string())
} else if is_pg_compatibility_test {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I am not sure the super high precision floating point results are necessary. I wonder if we could simply use the lower precision normalization in all the tests (both in pgcompat and non pg compat) 🤔

)
}

pub async fn connect_with_retry(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

I think in general the approach of orchestrating the docker containers using github CI worked well rather than restarting the containers within the tests

}};
}

fn cell_to_string(row: &Row, column: &Column, idx: usize) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Member

@xudong963 xudong963 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is a huge step! Thanks @melgenek

I agree with @alamb , we can iterate it at next PRs.

@avantgardnerio
Copy link
Contributor

  1. Don't orchestrate the postgres containers with rust test code

Good catch... I 💯 % agree with this.

@alamb
Copy link
Contributor

alamb commented Jan 20, 2023

🤔 it seems like risinglightdb/sqllogictest-rs#154 may be related to this PR (they have factored out their own postgres driver)

I think I can make time to help this PR along this weekend if that would assist.

@melgenek
Copy link
Contributor Author

I have several changes I would like to make, but I also think we could make them as (fast) follow on PRs. Specifically:

  1. Don't orchestrate the postgres containers with rust test code, rather orchestrate outside (either with an existing container or github CI, and document what is needed). Making this change will avoid requiring docker to run the datafusion tests,
  2. Use the same normalization in postgres and non-postgres code (maybe by decreasing fidelity of datafusion test)
  3. Remove existing postgres python integration tests

This sounds reasonable to me. I'll try to make prs for points 1 and 2 in the next few days.
For now, I merge master into this branch, so that it is mergeable.

🤔 it seems like risinglightdb/sqllogictest-rs#154 may be related to this PR (they have factored out their own postgres driver)

I saw this change, but I don't think that extraction is currently sufficient to fully replace implementation in this pr. The biggest part of the Postgres client is the results conversion. And if it is made pluggable into the client from sqllogictest-rs, then it would be almost like writing the whole client from scratch.
It seems to me, that a good idea would be to make primitive type conversions pluggable, so that Datafusion can apply the same floating point approximations, etc. And sqllogictest-rs would use the primitive type conversions for composite types, like arrays.

@alamb
Copy link
Contributor

alamb commented Jan 20, 2023

For now, I merge master into this branch, so that it is mergeable.

Thanks @melgenek . I filed issues for the follow on tasks:

@alamb
Copy link
Contributor

alamb commented Jan 20, 2023

Assuming this PR passes CI checks I plan to merge

@xudong963
Copy link
Member

merged, let's iterate!

@xudong963 xudong963 merged commit 1d69f28 into apache:master Jan 21, 2023
@ursabot
Copy link

ursabot commented Jan 21, 2023

Benchmark runs are scheduled for baseline = b71cae8 and contender = 1d69f28. 1d69f28 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Skipped ⚠️ Benchmarking of arrow-datafusion-commits is not supported on ec2-t3-xlarge-us-east-2] ec2-t3-xlarge-us-east-2
[Skipped ⚠️ Benchmarking of arrow-datafusion-commits is not supported on test-mac-arm] test-mac-arm
[Skipped ⚠️ Benchmarking of arrow-datafusion-commits is not supported on ursa-i9-9960x] ursa-i9-9960x
[Skipped ⚠️ Benchmarking of arrow-datafusion-commits is not supported on ursa-thinkcentre-m75q] ursa-thinkcentre-m75q
Buildkite builds:
Supported benchmarks:
ec2-t3-xlarge-us-east-2: Supported benchmark langs: Python, R. Runs only benchmarks with cloud = True
test-mac-arm: Supported benchmark langs: C++, Python, R
ursa-i9-9960x: Supported benchmark langs: Python, R, JavaScript
ursa-thinkcentre-m75q: Supported benchmark langs: C++, Java

@alamb
Copy link
Contributor

alamb commented Jan 21, 2023

Thanks again @melgenek and @xudong963 -- great to see this pushed along

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Core DataFusion crate sqllogictest SQL Logic Tests (.slt)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Replace python based integration test with sqllogictest
5 participants