├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── README.md ├── datafusion-postgres-cli ├── Cargo.toml ├── LICENSE-APACHE └── src │ └── main.rs ├── datafusion-postgres ├── Cargo.toml ├── LICENSE-APACHE └── src │ ├── datatypes.rs │ ├── encoder │ ├── list_encoder.rs │ ├── mod.rs │ ├── row_encoder.rs │ └── struct_encoder.rs │ ├── handlers.rs │ ├── information_schema.rs │ └── lib.rs ├── flake.lock ├── flake.nix └── tests-integration ├── all_types.parquet ├── create_arrow_testfile.py ├── delhiclimate.csv ├── test.py ├── test.sh └── test_all_types.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "02:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | schedule: [{cron: "30 13 * * *"}] 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | format: 11 | name: Rustfmt 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | components: rustfmt 19 | override: true 20 | - run: cargo fmt -- --check 21 | 22 | lint: 23 | name: Clippy lint 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: stable 30 | components: clippy 31 | override: true 32 | - name: Lint 33 | run: cargo clippy --all-features -- -D warnings 34 | 35 | test: 36 | name: Test 37 | runs-on: ${{ matrix.os }} 38 | strategy: 39 | matrix: 40 | build: [stable, nightly] 41 | include: 42 | - build: stable 43 | os: ubuntu-latest 44 | rust: stable 45 | - build: nightly 46 | os: ubuntu-latest 47 | rust: nightly 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: actions-rs/toolchain@v1 51 | with: 52 | toolchain: ${{ matrix.rust }} 53 | override: true 54 | - name: Build and run tests 55 | run: cargo test --all-features 56 | 57 | integration: 58 | name: Integration tests 59 | runs-on: ubuntu-latest 60 | timeout-minutes: 15 61 | needs: [test] 62 | steps: 63 | - uses: actions/checkout@v4 64 | - uses: actions-rs/toolchain@v1 65 | with: 66 | toolchain: stable 67 | override: true 68 | - run: | 69 | pip install psycopg 70 | - run: ./tests-integration/test.sh 71 | 72 | msrv: 73 | name: MSRV 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/checkout@v4 77 | - uses: actions-rs/toolchain@v1 78 | with: 79 | toolchain: "1.82.0" 80 | override: true 81 | - run: cargo build --all-features 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .direnv 3 | .envrc 4 | .vscode -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["datafusion-postgres", "datafusion-postgres-cli"] 4 | 5 | [workspace.dependencies] 6 | pgwire = "0.28" 7 | datafusion = { version = "46", default-features = false } 8 | tokio = { version = "1", default-features = false } 9 | 10 | [profile.release] 11 | strip = true 12 | opt-level = "z" 13 | lto = true 14 | codegen-units = 1 15 | panic = "abort" 16 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2018] [Ning Sun] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # datafusion-postgres 2 | 3 | ![Crates.io Version](https://img.shields.io/crates/v/datafusion-postgres?label=datafusion-postgres) 4 | 5 | Serving any [datafusion](https://datafusion.apache.org) `SessionContext` in 6 | Postgres protocol. Available as a library and a cli tool. 7 | 8 | This project is to add a [postgresql compatible access 9 | layer](https://github.com/sunng87/pgwire) to the [Apache 10 | Datafusion](https://github.com/apache/arrow-datafusion) query engine. 11 | 12 | It was originally an example of the [pgwire](https://github.com/sunng87/pgwire) 13 | project. 14 | 15 | ## Roadmap 16 | 17 | This project is in its very early stage, feel free to join the development by 18 | picking up unfinished items. 19 | 20 | - [x] datafusion-postgres as a CLI tool 21 | - [x] datafusion-postgres as a library 22 | - [x] datafusion information schema: a postgres compatible `information_schema` 23 | - [ ] datafusion pg catalog: a postgres compatible `pg_catalog` 24 | - [ ] data type mapping between arrow and postgres: in progress 25 | - [ ] additional postgres functions for datafusion 26 | 27 | ## Usage 28 | 29 | As a command-line application, this tool serves any JSON/CSV/Arrow/Parquet/Avro 30 | files as table, and expose them via Postgres compatible protocol, with which you 31 | can connect using psql or language drivers to execute `SELECT` queries against 32 | them. 33 | 34 | ``` 35 | datafusion-postgres 0.1.0 36 | A postgres interface for datatfusion. Serve any CSV/JSON/Arrow files as tables. 37 | 38 | USAGE: 39 | datafusion-postgres [OPTIONS] 40 | 41 | FLAGS: 42 | -h, --help Prints help information 43 | -V, --version Prints version information 44 | 45 | OPTIONS: 46 | --arrow ... Arrow files to register as table, using syntax `table_name:file_path` 47 | --avro ... Avro files to register as table, using syntax `table_name:file_path` 48 | --csv ... CSV files to register as table, using syntax `table_name:file_path` 49 | --json ... JSON files to register as table, using syntax `table_name:file_path` 50 | --parquet ... Parquet files to register as table, using syntax `table_name:file_path` 51 | ``` 52 | 53 | For example, we use this command to host `ETTm1.csv` dataset as table `ettm1`. 54 | 55 | ``` 56 | datafusion-postgres -c ettm1:ETTm1.csv 57 | Loaded ETTm1.csv as table ettm1 58 | Listening to 127.0.0.1:5432 59 | 60 | ``` 61 | 62 | Then connect to it via `psql`: 63 | 64 | ``` 65 | psql -h 127.0.0.1 -p 5432 -U postgres 66 | psql (16.2, server 0.20.0) 67 | WARNING: psql major version 16, server major version 0.20. 68 | Some psql features might not work. 69 | Type "help" for help. 70 | 71 | postgres=> select * from ettm1 limit 10; 72 | date | HUFL | HULL | MUFL | MULL | LUFL | LULL | OT 73 | ----------------------------+--------------------+--------------------+--------------------+---------------------+-------------------+--------------------+-------------------- 74 | 2016-07-01 00:00:00.000000 | 5.827000141143799 | 2.009000062942505 | 1.5989999771118164 | 0.4620000123977661 | 4.203000068664552 | 1.3400000333786009 | 30.5310001373291 75 | 2016-07-01 00:15:00.000000 | 5.760000228881836 | 2.075999975204468 | 1.4919999837875366 | 0.4259999990463257 | 4.263999938964844 | 1.4010000228881836 | 30.459999084472656 76 | 2016-07-01 00:30:00.000000 | 5.760000228881836 | 1.9420000314712524 | 1.4919999837875366 | 0.3910000026226044 | 4.234000205993652 | 1.309999942779541 | 30.038000106811523 77 | 2016-07-01 00:45:00.000000 | 5.760000228881836 | 1.9420000314712524 | 1.4919999837875366 | 0.4259999990463257 | 4.234000205993652 | 1.309999942779541 | 27.01300048828125 78 | 2016-07-01 01:00:00.000000 | 5.692999839782715 | 2.075999975204468 | 1.4919999837875366 | 0.4259999990463257 | 4.142000198364259 | 1.371000051498413 | 27.78700065612793 79 | 2016-07-01 01:15:00.000000 | 5.492000102996826 | 1.9420000314712524 | 1.4570000171661377 | 0.3910000026226044 | 4.111999988555908 | 1.2790000438690186 | 27.716999053955078 80 | 2016-07-01 01:30:00.000000 | 5.357999801635742 | 1.875 | 1.350000023841858 | 0.35499998927116394 | 3.928999900817871 | 1.3400000333786009 | 27.645999908447266 81 | 2016-07-01 01:45:00.000000 | 5.1570000648498535 | 1.8079999685287482 | 1.350000023841858 | 0.3199999928474426 | 3.806999921798706 | 1.2790000438690186 | 27.083999633789066 82 | 2016-07-01 02:00:00.000000 | 5.1570000648498535 | 1.741000056266785 | 1.2790000438690186 | 0.35499998927116394 | 3.776999950408936 | 1.218000054359436 | 27.78700065612793 83 | 2016-07-01 02:15:00.000000 | 5.1570000648498535 | 1.8079999685287482 | 1.350000023841858 | 0.4259999990463257 | 3.776999950408936 | 1.187999963760376 | 27.506000518798828 84 | (10 rows) 85 | ``` 86 | 87 | ## License 88 | 89 | This library is released under Apache license. 90 | -------------------------------------------------------------------------------- /datafusion-postgres-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "datafusion-postgres-cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | rust-version = "1.82.0" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | pgwire = { workspace = true } 11 | datafusion = { workspace = true, default-features = true, features = ["avro"] } 12 | tokio = { workspace = true, features = ["full"] } 13 | datafusion-postgres = { path = "../datafusion-postgres" } 14 | structopt = { version = "0.3", default-features = false } 15 | -------------------------------------------------------------------------------- /datafusion-postgres-cli/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2018] [Ning Sun] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /datafusion-postgres-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use datafusion::execution::options::{ 4 | ArrowReadOptions, AvroReadOptions, CsvReadOptions, NdJsonReadOptions, ParquetReadOptions, 5 | }; 6 | use datafusion::prelude::SessionContext; 7 | use datafusion_postgres::{DfSessionService, HandlerFactory}; // Assuming the crate name is `datafusion_postgres` 8 | use pgwire::tokio::process_socket; 9 | use structopt::StructOpt; 10 | use tokio::net::TcpListener; 11 | 12 | #[derive(Debug, StructOpt)] 13 | #[structopt( 14 | name = "datafusion-postgres", 15 | about = "A postgres interface for datafusion. Serve any CSV/JSON/Arrow files as tables." 16 | )] 17 | struct Opt { 18 | /// CSV files to register as table, using syntax `table_name:file_path` 19 | #[structopt(long("csv"))] 20 | csv_tables: Vec, 21 | /// JSON files to register as table, using syntax `table_name:file_path` 22 | #[structopt(long("json"))] 23 | json_tables: Vec, 24 | /// Arrow files to register as table, using syntax `table_name:file_path` 25 | #[structopt(long("arrow"))] 26 | arrow_tables: Vec, 27 | /// Parquet files to register as table, using syntax `table_name:file_path` 28 | #[structopt(long("parquet"))] 29 | parquet_tables: Vec, 30 | /// Avro files to register as table, using syntax `table_name:file_path` 31 | #[structopt(long("avro"))] 32 | avro_tables: Vec, 33 | /// Port the server listens to, default to 5432 34 | #[structopt(short, default_value = "5432")] 35 | port: u16, 36 | /// Host address the server listens to, default to 127.0.0.1 37 | #[structopt(long("host"), default_value = "127.0.0.1")] 38 | host: String, 39 | } 40 | 41 | fn parse_table_def(table_def: &str) -> (&str, &str) { 42 | table_def 43 | .split_once(':') 44 | .expect("Use this pattern to register table: table_name:file_path") 45 | } 46 | 47 | #[tokio::main] 48 | async fn main() -> Result<(), Box> { 49 | let opts = Opt::from_args(); 50 | 51 | let session_context = SessionContext::new(); 52 | 53 | // Register CSV tables 54 | for (table_name, table_path) in opts.csv_tables.iter().map(|s| parse_table_def(s.as_ref())) { 55 | session_context 56 | .register_csv(table_name, table_path, CsvReadOptions::default()) 57 | .await 58 | .map_err(|e| format!("Failed to register CSV table '{}': {}", table_name, e))?; 59 | println!("Loaded {} as table {}", table_path, table_name); 60 | } 61 | 62 | // Register JSON tables 63 | for (table_name, table_path) in opts.json_tables.iter().map(|s| parse_table_def(s.as_ref())) { 64 | session_context 65 | .register_json(table_name, table_path, NdJsonReadOptions::default()) 66 | .await 67 | .map_err(|e| format!("Failed to register JSON table '{}': {}", table_name, e))?; 68 | println!("Loaded {} as table {}", table_path, table_name); 69 | } 70 | 71 | // Register Arrow tables 72 | for (table_name, table_path) in opts 73 | .arrow_tables 74 | .iter() 75 | .map(|s| parse_table_def(s.as_ref())) 76 | { 77 | session_context 78 | .register_arrow(table_name, table_path, ArrowReadOptions::default()) 79 | .await 80 | .map_err(|e| format!("Failed to register Arrow table '{}': {}", table_name, e))?; 81 | println!("Loaded {} as table {}", table_path, table_name); 82 | } 83 | 84 | // Register Parquet tables 85 | for (table_name, table_path) in opts 86 | .parquet_tables 87 | .iter() 88 | .map(|s| parse_table_def(s.as_ref())) 89 | { 90 | session_context 91 | .register_parquet(table_name, table_path, ParquetReadOptions::default()) 92 | .await 93 | .map_err(|e| format!("Failed to register Parquet table '{}': {}", table_name, e))?; 94 | println!("Loaded {} as table {}", table_path, table_name); 95 | } 96 | 97 | // Register Avro tables 98 | for (table_name, table_path) in opts.avro_tables.iter().map(|s| parse_table_def(s.as_ref())) { 99 | session_context 100 | .register_avro(table_name, table_path, AvroReadOptions::default()) 101 | .await 102 | .map_err(|e| format!("Failed to register Avro table '{}': {}", table_name, e))?; 103 | println!("Loaded {} as table {}", table_path, table_name); 104 | } 105 | 106 | // Get the first catalog name from the session context 107 | let catalog_name = session_context 108 | .catalog_names() // Fixed: Removed .catalog_list() 109 | .first() 110 | .cloned(); 111 | 112 | // Create the handler factory with the session context and catalog name 113 | let factory = Arc::new(HandlerFactory(Arc::new(DfSessionService::new( 114 | session_context, 115 | catalog_name, 116 | )))); 117 | 118 | // Bind to the specified host and port 119 | let server_addr = format!("{}:{}", opts.host, opts.port); 120 | let listener = TcpListener::bind(&server_addr).await?; 121 | println!("Listening on {}", server_addr); 122 | 123 | // Accept incoming connections 124 | loop { 125 | let (socket, addr) = listener.accept().await?; 126 | let factory_ref = factory.clone(); 127 | println!("Accepted connection from {}", addr); 128 | 129 | tokio::spawn(async move { 130 | if let Err(e) = process_socket(socket, None, factory_ref).await { 131 | eprintln!("Error processing socket: {}", e); 132 | } 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /datafusion-postgres/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "datafusion-postgres" 3 | version = "0.3.0" 4 | edition = "2021" 5 | rust-version = "1.82.0" 6 | description = "Exporting datafusion query engine with postgres wire protocol" 7 | authors = ["Ning Sun "] 8 | license = "Apache-2.0" 9 | keywords = ["database", "postgresql"] 10 | homepage = "https://github.com/sunng87/datafusion-postgres/" 11 | repository = "https://github.com/sunng87/datafusion-postgres/" 12 | documentation = "https://docs.rs/crate/datafusion-postgres/" 13 | readme = "../README.md" 14 | 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | arrow = "54.2.0" 20 | async-trait = "0.1" 21 | bytes = "1.10.1" 22 | chrono = { version = "0.4", features = ["std"] } 23 | datafusion = { workspace = true } 24 | futures = "0.3" 25 | log = "0.4" 26 | pgwire = { workspace = true } 27 | postgres-types = "0.2" 28 | rust_decimal = { version = "1.37", features = ["db-postgres"] } 29 | tokio = { version = "1.45", features = ["sync"] } 30 | -------------------------------------------------------------------------------- /datafusion-postgres/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2018] [Ning Sun] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /datafusion-postgres/src/datatypes.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | use std::sync::Arc; 3 | 4 | use chrono::{DateTime, FixedOffset}; 5 | use chrono::{NaiveDate, NaiveDateTime}; 6 | use datafusion::arrow::datatypes::*; 7 | use datafusion::arrow::record_batch::RecordBatch; 8 | use datafusion::common::{DFSchema, ParamValues}; 9 | use datafusion::prelude::*; 10 | use datafusion::scalar::ScalarValue; 11 | use futures::{stream, StreamExt}; 12 | use pgwire::api::portal::{Format, Portal}; 13 | use pgwire::api::results::{FieldInfo, QueryResponse}; 14 | use pgwire::api::Type; 15 | use pgwire::error::{ErrorInfo, PgWireError, PgWireResult}; 16 | use pgwire::messages::data::DataRow; 17 | use postgres_types::Kind; 18 | use rust_decimal::prelude::ToPrimitive; 19 | use rust_decimal::Decimal; 20 | 21 | use crate::encoder::row_encoder::RowEncoder; 22 | 23 | pub(crate) fn into_pg_type(df_type: &DataType) -> PgWireResult { 24 | Ok(match df_type { 25 | DataType::Null => Type::UNKNOWN, 26 | DataType::Boolean => Type::BOOL, 27 | DataType::Int8 | DataType::UInt8 => Type::CHAR, 28 | DataType::Int16 | DataType::UInt16 => Type::INT2, 29 | DataType::Int32 | DataType::UInt32 => Type::INT4, 30 | DataType::Int64 | DataType::UInt64 => Type::INT8, 31 | DataType::Timestamp(_, tz) => { 32 | if tz.is_some() { 33 | Type::TIMESTAMPTZ 34 | } else { 35 | Type::TIMESTAMP 36 | } 37 | } 38 | DataType::Time32(_) | DataType::Time64(_) => Type::TIME, 39 | DataType::Date32 | DataType::Date64 => Type::DATE, 40 | DataType::Interval(_) => Type::INTERVAL, 41 | DataType::Binary | DataType::FixedSizeBinary(_) | DataType::LargeBinary => Type::BYTEA, 42 | DataType::Float16 | DataType::Float32 => Type::FLOAT4, 43 | DataType::Float64 => Type::FLOAT8, 44 | DataType::Decimal128(_, _) => Type::NUMERIC, 45 | DataType::Utf8 => Type::VARCHAR, 46 | DataType::LargeUtf8 => Type::TEXT, 47 | DataType::List(field) | DataType::FixedSizeList(field, _) | DataType::LargeList(field) => { 48 | match field.data_type() { 49 | DataType::Boolean => Type::BOOL_ARRAY, 50 | DataType::Int8 | DataType::UInt8 => Type::CHAR_ARRAY, 51 | DataType::Int16 | DataType::UInt16 => Type::INT2_ARRAY, 52 | DataType::Int32 | DataType::UInt32 => Type::INT4_ARRAY, 53 | DataType::Int64 | DataType::UInt64 => Type::INT8_ARRAY, 54 | DataType::Timestamp(_, tz) => { 55 | if tz.is_some() { 56 | Type::TIMESTAMPTZ_ARRAY 57 | } else { 58 | Type::TIMESTAMP_ARRAY 59 | } 60 | } 61 | DataType::Time32(_) | DataType::Time64(_) => Type::TIME_ARRAY, 62 | DataType::Date32 | DataType::Date64 => Type::DATE_ARRAY, 63 | DataType::Interval(_) => Type::INTERVAL_ARRAY, 64 | DataType::FixedSizeBinary(_) | DataType::Binary => Type::BYTEA_ARRAY, 65 | DataType::Float16 | DataType::Float32 => Type::FLOAT4_ARRAY, 66 | DataType::Float64 => Type::FLOAT8_ARRAY, 67 | DataType::Utf8 => Type::VARCHAR_ARRAY, 68 | DataType::LargeUtf8 => Type::TEXT_ARRAY, 69 | struct_type @ DataType::Struct(_) => Type::new( 70 | Type::RECORD_ARRAY.name().into(), 71 | Type::RECORD_ARRAY.oid(), 72 | Kind::Array(into_pg_type(struct_type)?), 73 | Type::RECORD_ARRAY.schema().into(), 74 | ), 75 | list_type => { 76 | return Err(PgWireError::UserError(Box::new(ErrorInfo::new( 77 | "ERROR".to_owned(), 78 | "XX000".to_owned(), 79 | format!("Unsupported List Datatype {list_type}"), 80 | )))); 81 | } 82 | } 83 | } 84 | DataType::Utf8View => Type::TEXT, 85 | DataType::Dictionary(_, value_type) => into_pg_type(value_type)?, 86 | DataType::Struct(fields) => { 87 | let name: String = fields 88 | .iter() 89 | .map(|x| x.name().clone()) 90 | .reduce(|a, b| a + ", " + &b) 91 | .map(|x| format!("({x})")) 92 | .unwrap_or("()".to_string()); 93 | let kind = Kind::Composite( 94 | fields 95 | .iter() 96 | .map(|x| { 97 | into_pg_type(x.data_type()) 98 | .map(|_type| postgres_types::Field::new(x.name().clone(), _type)) 99 | }) 100 | .collect::, PgWireError>>()?, 101 | ); 102 | Type::new(name, Type::RECORD.oid(), kind, Type::RECORD.schema().into()) 103 | } 104 | _ => { 105 | return Err(PgWireError::UserError(Box::new(ErrorInfo::new( 106 | "ERROR".to_owned(), 107 | "XX000".to_owned(), 108 | format!("Unsupported Datatype {df_type}"), 109 | )))); 110 | } 111 | }) 112 | } 113 | 114 | pub(crate) fn df_schema_to_pg_fields( 115 | schema: &DFSchema, 116 | format: &Format, 117 | ) -> PgWireResult> { 118 | schema 119 | .fields() 120 | .iter() 121 | .enumerate() 122 | .map(|(idx, f)| { 123 | let pg_type = into_pg_type(f.data_type())?; 124 | Ok(FieldInfo::new( 125 | f.name().into(), 126 | None, 127 | None, 128 | pg_type, 129 | format.format_for(idx), 130 | )) 131 | }) 132 | .collect::>>() 133 | } 134 | 135 | pub(crate) async fn encode_dataframe<'a>( 136 | df: DataFrame, 137 | format: &Format, 138 | ) -> PgWireResult> { 139 | let fields = Arc::new(df_schema_to_pg_fields(df.schema(), format)?); 140 | 141 | let recordbatch_stream = df 142 | .execute_stream() 143 | .await 144 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 145 | 146 | let fields_ref = fields.clone(); 147 | let pg_row_stream = recordbatch_stream 148 | .map(move |rb: datafusion::error::Result| { 149 | let row_stream: Box> + Send + Sync> = match rb 150 | { 151 | Ok(rb) => { 152 | let fields = fields_ref.clone(); 153 | let mut row_stream = RowEncoder::new(rb, fields); 154 | Box::new(std::iter::from_fn(move || row_stream.next_row())) 155 | } 156 | Err(e) => Box::new(iter::once(Err(PgWireError::ApiError(e.into())))), 157 | }; 158 | stream::iter(row_stream) 159 | }) 160 | .flatten(); 161 | Ok(QueryResponse::new(fields, pg_row_stream)) 162 | } 163 | 164 | /// Deserialize client provided parameter data. 165 | /// 166 | /// First we try to use the type information from `pg_type_hint`, which is 167 | /// provided by the client. 168 | /// If the type is empty or unknown, we fallback to datafusion inferenced type 169 | /// from `inferenced_types`. 170 | /// An error will be raised when neither sources can provide type information. 171 | pub(crate) fn deserialize_parameters( 172 | portal: &Portal, 173 | inferenced_types: &[Option<&DataType>], 174 | ) -> PgWireResult 175 | where 176 | S: Clone, 177 | { 178 | fn get_pg_type( 179 | pg_type_hint: Option<&Type>, 180 | inferenced_type: Option<&DataType>, 181 | ) -> PgWireResult { 182 | if let Some(ty) = pg_type_hint { 183 | Ok(ty.clone()) 184 | } else if let Some(infer_type) = inferenced_type { 185 | into_pg_type(infer_type) 186 | } else { 187 | Err(PgWireError::UserError(Box::new(ErrorInfo::new( 188 | "FATAL".to_string(), 189 | "XX000".to_string(), 190 | "Unknown parameter type".to_string(), 191 | )))) 192 | } 193 | } 194 | 195 | let param_len = portal.parameter_len(); 196 | let mut deserialized_params = Vec::with_capacity(param_len); 197 | for i in 0..param_len { 198 | let pg_type = get_pg_type( 199 | portal.statement.parameter_types.get(i), 200 | inferenced_types.get(i).and_then(|v| v.to_owned()), 201 | )?; 202 | match pg_type { 203 | // enumerate all supported parameter types and deserialize the 204 | // type to ScalarValue 205 | Type::BOOL => { 206 | let value = portal.parameter::(i, &pg_type)?; 207 | deserialized_params.push(ScalarValue::Boolean(value)); 208 | } 209 | Type::CHAR => { 210 | let value = portal.parameter::(i, &pg_type)?; 211 | deserialized_params.push(ScalarValue::Int8(value)); 212 | } 213 | Type::INT2 => { 214 | let value = portal.parameter::(i, &pg_type)?; 215 | deserialized_params.push(ScalarValue::Int16(value)); 216 | } 217 | Type::INT4 => { 218 | let value = portal.parameter::(i, &pg_type)?; 219 | deserialized_params.push(ScalarValue::Int32(value)); 220 | } 221 | Type::INT8 => { 222 | let value = portal.parameter::(i, &pg_type)?; 223 | deserialized_params.push(ScalarValue::Int64(value)); 224 | } 225 | Type::TEXT | Type::VARCHAR => { 226 | let value = portal.parameter::(i, &pg_type)?; 227 | deserialized_params.push(ScalarValue::Utf8(value)); 228 | } 229 | Type::BYTEA => { 230 | let value = portal.parameter::>(i, &pg_type)?; 231 | deserialized_params.push(ScalarValue::Binary(value)); 232 | } 233 | 234 | Type::FLOAT4 => { 235 | let value = portal.parameter::(i, &pg_type)?; 236 | deserialized_params.push(ScalarValue::Float32(value)); 237 | } 238 | Type::FLOAT8 => { 239 | let value = portal.parameter::(i, &pg_type)?; 240 | deserialized_params.push(ScalarValue::Float64(value)); 241 | } 242 | Type::NUMERIC => { 243 | let value = match portal.parameter::(i, &pg_type)? { 244 | None => ScalarValue::Decimal128(None, 0, 0), 245 | Some(value) => { 246 | let precision = match value.mantissa() { 247 | 0 => 1, 248 | m => (m.abs() as f64).log10().floor() as u8 + 1, 249 | }; 250 | let scale = value.scale() as i8; 251 | ScalarValue::Decimal128(value.to_i128(), precision, scale) 252 | } 253 | }; 254 | deserialized_params.push(value); 255 | } 256 | Type::TIMESTAMP => { 257 | let value = portal.parameter::(i, &pg_type)?; 258 | deserialized_params.push(ScalarValue::TimestampMicrosecond( 259 | value.map(|t| t.and_utc().timestamp_micros()), 260 | None, 261 | )); 262 | } 263 | Type::TIMESTAMPTZ => { 264 | let value = portal.parameter::>(i, &pg_type)?; 265 | deserialized_params.push(ScalarValue::TimestampMicrosecond( 266 | value.map(|t| t.timestamp_micros()), 267 | value.map(|t| t.offset().to_string().into()), 268 | )); 269 | } 270 | Type::DATE => { 271 | let value = portal.parameter::(i, &pg_type)?; 272 | deserialized_params 273 | .push(ScalarValue::Date32(value.map(Date32Type::from_naive_date))); 274 | } 275 | // TODO: add more types 276 | _ => { 277 | return Err(PgWireError::UserError(Box::new(ErrorInfo::new( 278 | "FATAL".to_string(), 279 | "XX000".to_string(), 280 | format!("Unsupported parameter type: {}", pg_type), 281 | )))); 282 | } 283 | } 284 | } 285 | 286 | Ok(ParamValues::List(deserialized_params)) 287 | } 288 | -------------------------------------------------------------------------------- /datafusion-postgres/src/encoder/list_encoder.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, str::FromStr, sync::Arc}; 2 | 3 | use arrow::{ 4 | datatypes::{ 5 | Date32Type, Date64Type, Time32MillisecondType, Time32SecondType, Time64MicrosecondType, 6 | Time64NanosecondType, 7 | }, 8 | temporal_conversions::{as_date, as_time}, 9 | }; 10 | use bytes::{BufMut, BytesMut}; 11 | use chrono::{DateTime, TimeZone, Utc}; 12 | use datafusion::arrow::{ 13 | array::{ 14 | timezone::Tz, Array, BinaryArray, BooleanArray, Date32Array, Date64Array, Decimal128Array, 15 | LargeBinaryArray, PrimitiveArray, StringArray, Time32MillisecondArray, Time32SecondArray, 16 | Time64MicrosecondArray, Time64NanosecondArray, TimestampMicrosecondArray, 17 | TimestampMillisecondArray, TimestampNanosecondArray, TimestampSecondArray, 18 | }, 19 | datatypes::{ 20 | DataType, Float32Type, Float64Type, Int16Type, Int32Type, Int64Type, Int8Type, TimeUnit, 21 | UInt16Type, UInt32Type, UInt64Type, UInt8Type, 22 | }, 23 | }; 24 | use pgwire::{ 25 | api::results::FieldFormat, 26 | error::{ErrorInfo, PgWireError}, 27 | types::{ToSqlText, QUOTE_ESCAPE}, 28 | }; 29 | use postgres_types::{ToSql, Type}; 30 | use rust_decimal::Decimal; 31 | 32 | use super::{struct_encoder::encode_struct, EncodedValue}; 33 | 34 | fn get_bool_list_value(arr: &Arc) -> Vec> { 35 | arr.as_any() 36 | .downcast_ref::() 37 | .unwrap() 38 | .iter() 39 | .collect() 40 | } 41 | 42 | macro_rules! get_primitive_list_value { 43 | ($name:ident, $t:ty, $pt:ty) => { 44 | fn $name(arr: &Arc) -> Vec> { 45 | arr.as_any() 46 | .downcast_ref::>() 47 | .unwrap() 48 | .iter() 49 | .collect() 50 | } 51 | }; 52 | 53 | ($name:ident, $t:ty, $pt:ty, $f:expr) => { 54 | fn $name(arr: &Arc) -> Vec> { 55 | arr.as_any() 56 | .downcast_ref::>() 57 | .unwrap() 58 | .iter() 59 | .map(|val| val.map($f)) 60 | .collect() 61 | } 62 | }; 63 | } 64 | 65 | get_primitive_list_value!(get_i8_list_value, Int8Type, i8); 66 | get_primitive_list_value!(get_i16_list_value, Int16Type, i16); 67 | get_primitive_list_value!(get_i32_list_value, Int32Type, i32); 68 | get_primitive_list_value!(get_i64_list_value, Int64Type, i64); 69 | get_primitive_list_value!(get_u8_list_value, UInt8Type, i8, |val: u8| { val as i8 }); 70 | get_primitive_list_value!(get_u16_list_value, UInt16Type, i16, |val: u16| { 71 | val as i16 72 | }); 73 | get_primitive_list_value!(get_u32_list_value, UInt32Type, u32); 74 | get_primitive_list_value!(get_u64_list_value, UInt64Type, i64, |val: u64| { 75 | val as i64 76 | }); 77 | get_primitive_list_value!(get_f32_list_value, Float32Type, f32); 78 | get_primitive_list_value!(get_f64_list_value, Float64Type, f64); 79 | 80 | fn encode_field( 81 | t: &[T], 82 | type_: &Type, 83 | format: FieldFormat, 84 | ) -> Result> { 85 | let mut bytes = BytesMut::new(); 86 | match format { 87 | FieldFormat::Text => t.to_sql_text(type_, &mut bytes)?, 88 | FieldFormat::Binary => t.to_sql(type_, &mut bytes)?, 89 | }; 90 | Ok(EncodedValue { bytes }) 91 | } 92 | 93 | pub(crate) fn encode_list( 94 | arr: Arc, 95 | type_: &Type, 96 | format: FieldFormat, 97 | ) -> Result> { 98 | match arr.data_type() { 99 | DataType::Null => { 100 | let mut bytes = BytesMut::new(); 101 | match format { 102 | FieldFormat::Text => None::.to_sql_text(type_, &mut bytes), 103 | FieldFormat::Binary => None::.to_sql(type_, &mut bytes), 104 | }?; 105 | Ok(EncodedValue { bytes }) 106 | } 107 | DataType::Boolean => encode_field(&get_bool_list_value(&arr), type_, format), 108 | DataType::Int8 => encode_field(&get_i8_list_value(&arr), type_, format), 109 | DataType::Int16 => encode_field(&get_i16_list_value(&arr), type_, format), 110 | DataType::Int32 => encode_field(&get_i32_list_value(&arr), type_, format), 111 | DataType::Int64 => encode_field(&get_i64_list_value(&arr), type_, format), 112 | DataType::UInt8 => encode_field(&get_u8_list_value(&arr), type_, format), 113 | DataType::UInt16 => encode_field(&get_u16_list_value(&arr), type_, format), 114 | DataType::UInt32 => encode_field(&get_u32_list_value(&arr), type_, format), 115 | DataType::UInt64 => encode_field(&get_u64_list_value(&arr), type_, format), 116 | DataType::Float32 => encode_field(&get_f32_list_value(&arr), type_, format), 117 | DataType::Float64 => encode_field(&get_f64_list_value(&arr), type_, format), 118 | DataType::Decimal128(_, s) => { 119 | let value: Vec<_> = arr 120 | .as_any() 121 | .downcast_ref::() 122 | .unwrap() 123 | .iter() 124 | .map(|ov| ov.map(|v| Decimal::from_i128_with_scale(v, *s as u32))) 125 | .collect(); 126 | encode_field(&value, type_, format) 127 | } 128 | DataType::Utf8 => { 129 | let value: Vec> = arr 130 | .as_any() 131 | .downcast_ref::() 132 | .unwrap() 133 | .iter() 134 | .collect(); 135 | encode_field(&value, type_, format) 136 | } 137 | DataType::Binary => { 138 | let value: Vec> = arr 139 | .as_any() 140 | .downcast_ref::() 141 | .unwrap() 142 | .iter() 143 | .collect(); 144 | encode_field(&value, type_, format) 145 | } 146 | DataType::LargeBinary => { 147 | let value: Vec> = arr 148 | .as_any() 149 | .downcast_ref::() 150 | .unwrap() 151 | .iter() 152 | .collect(); 153 | encode_field(&value, type_, format) 154 | } 155 | 156 | DataType::Date32 => { 157 | let value: Vec> = arr 158 | .as_any() 159 | .downcast_ref::() 160 | .unwrap() 161 | .iter() 162 | .map(|val| val.and_then(|x| as_date::(x as i64))) 163 | .collect(); 164 | encode_field(&value, type_, format) 165 | } 166 | DataType::Date64 => { 167 | let value: Vec> = arr 168 | .as_any() 169 | .downcast_ref::() 170 | .unwrap() 171 | .iter() 172 | .map(|val| val.and_then(as_date::)) 173 | .collect(); 174 | encode_field(&value, type_, format) 175 | } 176 | DataType::Time32(unit) => match unit { 177 | TimeUnit::Second => { 178 | let value: Vec> = arr 179 | .as_any() 180 | .downcast_ref::() 181 | .unwrap() 182 | .iter() 183 | .map(|val| val.and_then(|x| as_time::(x as i64))) 184 | .collect(); 185 | encode_field(&value, type_, format) 186 | } 187 | TimeUnit::Millisecond => { 188 | let value: Vec> = arr 189 | .as_any() 190 | .downcast_ref::() 191 | .unwrap() 192 | .iter() 193 | .map(|val| val.and_then(|x| as_time::(x as i64))) 194 | .collect(); 195 | encode_field(&value, type_, format) 196 | } 197 | _ => { 198 | unimplemented!() 199 | } 200 | }, 201 | DataType::Time64(unit) => match unit { 202 | TimeUnit::Microsecond => { 203 | let value: Vec> = arr 204 | .as_any() 205 | .downcast_ref::() 206 | .unwrap() 207 | .iter() 208 | .map(|val| val.and_then(as_time::)) 209 | .collect(); 210 | encode_field(&value, type_, format) 211 | } 212 | TimeUnit::Nanosecond => { 213 | let value: Vec> = arr 214 | .as_any() 215 | .downcast_ref::() 216 | .unwrap() 217 | .iter() 218 | .map(|val| val.and_then(as_time::)) 219 | .collect(); 220 | encode_field(&value, type_, format) 221 | } 222 | _ => { 223 | unimplemented!() 224 | } 225 | }, 226 | DataType::Timestamp(unit, timezone) => match unit { 227 | TimeUnit::Second => { 228 | let array_iter = arr 229 | .as_any() 230 | .downcast_ref::() 231 | .unwrap() 232 | .iter(); 233 | 234 | if let Some(tz) = timezone { 235 | let tz = Tz::from_str(tz.as_ref()) 236 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 237 | let value: Vec<_> = array_iter 238 | .map(|i| { 239 | i.and_then(|i| { 240 | DateTime::from_timestamp(i, 0).map(|dt| { 241 | Utc.from_utc_datetime(&dt.naive_utc()) 242 | .with_timezone(&tz) 243 | .fixed_offset() 244 | }) 245 | }) 246 | }) 247 | .collect(); 248 | encode_field(&value, type_, format) 249 | } else { 250 | let value: Vec<_> = array_iter 251 | .map(|i| { 252 | i.and_then(|i| DateTime::from_timestamp(i, 0).map(|dt| dt.naive_utc())) 253 | }) 254 | .collect(); 255 | encode_field(&value, type_, format) 256 | } 257 | } 258 | TimeUnit::Millisecond => { 259 | let array_iter = arr 260 | .as_any() 261 | .downcast_ref::() 262 | .unwrap() 263 | .iter(); 264 | 265 | if let Some(tz) = timezone { 266 | let tz = Tz::from_str(tz.as_ref()) 267 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 268 | let value: Vec<_> = array_iter 269 | .map(|i| { 270 | i.and_then(|i| { 271 | DateTime::from_timestamp_millis(i).map(|dt| { 272 | Utc.from_utc_datetime(&dt.naive_utc()) 273 | .with_timezone(&tz) 274 | .fixed_offset() 275 | }) 276 | }) 277 | }) 278 | .collect(); 279 | encode_field(&value, type_, format) 280 | } else { 281 | let value: Vec<_> = array_iter 282 | .map(|i| { 283 | i.and_then(|i| { 284 | DateTime::from_timestamp_millis(i).map(|dt| dt.naive_utc()) 285 | }) 286 | }) 287 | .collect(); 288 | encode_field(&value, type_, format) 289 | } 290 | } 291 | TimeUnit::Microsecond => { 292 | let array_iter = arr 293 | .as_any() 294 | .downcast_ref::() 295 | .unwrap() 296 | .iter(); 297 | 298 | if let Some(tz) = timezone { 299 | let tz = Tz::from_str(tz.as_ref()) 300 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 301 | let value: Vec<_> = array_iter 302 | .map(|i| { 303 | i.and_then(|i| { 304 | DateTime::from_timestamp_micros(i).map(|dt| { 305 | Utc.from_utc_datetime(&dt.naive_utc()) 306 | .with_timezone(&tz) 307 | .fixed_offset() 308 | }) 309 | }) 310 | }) 311 | .collect(); 312 | encode_field(&value, type_, format) 313 | } else { 314 | let value: Vec<_> = array_iter 315 | .map(|i| { 316 | i.and_then(|i| { 317 | DateTime::from_timestamp_micros(i).map(|dt| dt.naive_utc()) 318 | }) 319 | }) 320 | .collect(); 321 | encode_field(&value, type_, format) 322 | } 323 | } 324 | TimeUnit::Nanosecond => { 325 | let array_iter = arr 326 | .as_any() 327 | .downcast_ref::() 328 | .unwrap() 329 | .iter(); 330 | 331 | if let Some(tz) = timezone { 332 | let tz = Tz::from_str(tz.as_ref()) 333 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 334 | let value: Vec<_> = array_iter 335 | .map(|i| { 336 | i.map(|i| { 337 | Utc.from_utc_datetime( 338 | &DateTime::from_timestamp_nanos(i).naive_utc(), 339 | ) 340 | .with_timezone(&tz) 341 | .fixed_offset() 342 | }) 343 | }) 344 | .collect(); 345 | encode_field(&value, type_, format) 346 | } else { 347 | let value: Vec<_> = array_iter 348 | .map(|i| i.map(|i| DateTime::from_timestamp_nanos(i).naive_utc())) 349 | .collect(); 350 | encode_field(&value, type_, format) 351 | } 352 | } 353 | }, 354 | DataType::Struct(_) => { 355 | let fields = match type_.kind() { 356 | postgres_types::Kind::Array(struct_type_) => Ok(struct_type_), 357 | _ => Err(format!( 358 | "Expected list type found type {} of kind {:?}", 359 | type_, 360 | type_.kind() 361 | )), 362 | } 363 | .and_then(|struct_type| match struct_type.kind() { 364 | postgres_types::Kind::Composite(fields) => Ok(fields), 365 | _ => Err(format!( 366 | "Failed to unwrap a composite type inside from type {} kind {:?}", 367 | type_, 368 | type_.kind() 369 | )), 370 | }) 371 | .map_err(|err| { 372 | let err = ErrorInfo::new("ERROR".to_owned(), "XX000".to_owned(), err); 373 | Box::new(PgWireError::UserError(Box::new(err))) 374 | })?; 375 | 376 | let values: Result, _> = (0..arr.len()) 377 | .map(|row| encode_struct(&arr, row, fields, format)) 378 | .map(|x| { 379 | if matches!(format, FieldFormat::Text) { 380 | x.map(|opt| { 381 | opt.map(|value| { 382 | let mut w = BytesMut::new(); 383 | w.put_u8(b'"'); 384 | w.put_slice( 385 | QUOTE_ESCAPE 386 | .replace_all( 387 | &String::from_utf8_lossy(&value.bytes), 388 | r#"\$1"#, 389 | ) 390 | .as_bytes(), 391 | ); 392 | w.put_u8(b'"'); 393 | EncodedValue { bytes: w } 394 | }) 395 | }) 396 | } else { 397 | x 398 | } 399 | }) 400 | .collect(); 401 | encode_field(&values?, type_, format) 402 | } 403 | // TODO: more types 404 | list_type => { 405 | let err = PgWireError::UserError(Box::new(ErrorInfo::new( 406 | "ERROR".to_owned(), 407 | "XX000".to_owned(), 408 | format!( 409 | "Unsupported List Datatype {} and array {:?}", 410 | list_type, &arr 411 | ), 412 | ))); 413 | 414 | Err(Box::new(err)) 415 | } 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /datafusion-postgres/src/encoder/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::str::FromStr; 3 | use std::sync::Arc; 4 | 5 | use bytes::BufMut; 6 | use bytes::BytesMut; 7 | use chrono::{NaiveDate, NaiveDateTime}; 8 | use datafusion::arrow::array::*; 9 | use datafusion::arrow::datatypes::*; 10 | use list_encoder::encode_list; 11 | use pgwire::api::results::DataRowEncoder; 12 | use pgwire::api::results::FieldFormat; 13 | use pgwire::error::{ErrorInfo, PgWireError, PgWireResult}; 14 | use pgwire::types::ToSqlText; 15 | use postgres_types::{ToSql, Type}; 16 | use rust_decimal::Decimal; 17 | use struct_encoder::encode_struct; 18 | use timezone::Tz; 19 | 20 | pub mod list_encoder; 21 | pub mod row_encoder; 22 | pub mod struct_encoder; 23 | 24 | trait Encoder { 25 | fn encode_field_with_type_and_format( 26 | &mut self, 27 | value: &T, 28 | data_type: &Type, 29 | format: FieldFormat, 30 | ) -> PgWireResult<()> 31 | where 32 | T: ToSql + ToSqlText + Sized; 33 | } 34 | 35 | impl Encoder for DataRowEncoder { 36 | fn encode_field_with_type_and_format( 37 | &mut self, 38 | value: &T, 39 | data_type: &Type, 40 | format: FieldFormat, 41 | ) -> PgWireResult<()> 42 | where 43 | T: ToSql + ToSqlText + Sized, 44 | { 45 | self.encode_field_with_type_and_format(value, data_type, format) 46 | } 47 | } 48 | 49 | pub(crate) struct EncodedValue { 50 | pub(crate) bytes: BytesMut, 51 | } 52 | 53 | impl std::fmt::Debug for EncodedValue { 54 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 55 | f.debug_struct("EncodedValue").finish() 56 | } 57 | } 58 | 59 | impl ToSql for EncodedValue { 60 | fn to_sql( 61 | &self, 62 | _ty: &Type, 63 | out: &mut BytesMut, 64 | ) -> Result> 65 | where 66 | Self: Sized, 67 | { 68 | out.writer().write_all(&self.bytes)?; 69 | Ok(postgres_types::IsNull::No) 70 | } 71 | 72 | fn accepts(_ty: &Type) -> bool 73 | where 74 | Self: Sized, 75 | { 76 | true 77 | } 78 | 79 | fn to_sql_checked( 80 | &self, 81 | ty: &Type, 82 | out: &mut BytesMut, 83 | ) -> Result> { 84 | self.to_sql(ty, out) 85 | } 86 | } 87 | 88 | impl ToSqlText for EncodedValue { 89 | fn to_sql_text( 90 | &self, 91 | _ty: &Type, 92 | out: &mut BytesMut, 93 | ) -> Result> 94 | where 95 | Self: Sized, 96 | { 97 | out.writer().write_all(&self.bytes)?; 98 | Ok(postgres_types::IsNull::No) 99 | } 100 | } 101 | 102 | fn get_bool_value(arr: &Arc, idx: usize) -> Option { 103 | (!arr.is_null(idx)).then(|| { 104 | arr.as_any() 105 | .downcast_ref::() 106 | .unwrap() 107 | .value(idx) 108 | }) 109 | } 110 | 111 | macro_rules! get_primitive_value { 112 | ($name:ident, $t:ty, $pt:ty) => { 113 | fn $name(arr: &Arc, idx: usize) -> Option<$pt> { 114 | (!arr.is_null(idx)).then(|| { 115 | arr.as_any() 116 | .downcast_ref::>() 117 | .unwrap() 118 | .value(idx) 119 | }) 120 | } 121 | }; 122 | } 123 | 124 | get_primitive_value!(get_i8_value, Int8Type, i8); 125 | get_primitive_value!(get_i16_value, Int16Type, i16); 126 | get_primitive_value!(get_i32_value, Int32Type, i32); 127 | get_primitive_value!(get_i64_value, Int64Type, i64); 128 | get_primitive_value!(get_u8_value, UInt8Type, u8); 129 | get_primitive_value!(get_u16_value, UInt16Type, u16); 130 | get_primitive_value!(get_u32_value, UInt32Type, u32); 131 | get_primitive_value!(get_u64_value, UInt64Type, u64); 132 | get_primitive_value!(get_f32_value, Float32Type, f32); 133 | get_primitive_value!(get_f64_value, Float64Type, f64); 134 | 135 | fn get_utf8_view_value(arr: &Arc, idx: usize) -> Option<&str> { 136 | (!arr.is_null(idx)).then(|| { 137 | arr.as_any() 138 | .downcast_ref::() 139 | .unwrap() 140 | .value(idx) 141 | }) 142 | } 143 | 144 | fn get_utf8_value(arr: &Arc, idx: usize) -> Option<&str> { 145 | (!arr.is_null(idx)).then(|| { 146 | arr.as_any() 147 | .downcast_ref::() 148 | .unwrap() 149 | .value(idx) 150 | }) 151 | } 152 | 153 | fn get_large_utf8_value(arr: &Arc, idx: usize) -> Option<&str> { 154 | (!arr.is_null(idx)).then(|| { 155 | arr.as_any() 156 | .downcast_ref::() 157 | .unwrap() 158 | .value(idx) 159 | }) 160 | } 161 | 162 | fn get_binary_value(arr: &Arc, idx: usize) -> Option<&[u8]> { 163 | (!arr.is_null(idx)).then(|| { 164 | arr.as_any() 165 | .downcast_ref::() 166 | .unwrap() 167 | .value(idx) 168 | }) 169 | } 170 | 171 | fn get_large_binary_value(arr: &Arc, idx: usize) -> Option<&[u8]> { 172 | (!arr.is_null(idx)).then(|| { 173 | arr.as_any() 174 | .downcast_ref::() 175 | .unwrap() 176 | .value(idx) 177 | }) 178 | } 179 | 180 | fn get_date32_value(arr: &Arc, idx: usize) -> Option { 181 | if arr.is_null(idx) { 182 | return None; 183 | } 184 | arr.as_any() 185 | .downcast_ref::() 186 | .unwrap() 187 | .value_as_date(idx) 188 | } 189 | 190 | fn get_date64_value(arr: &Arc, idx: usize) -> Option { 191 | if arr.is_null(idx) { 192 | return None; 193 | } 194 | arr.as_any() 195 | .downcast_ref::() 196 | .unwrap() 197 | .value_as_date(idx) 198 | } 199 | 200 | fn get_time32_second_value(arr: &Arc, idx: usize) -> Option { 201 | if arr.is_null(idx) { 202 | return None; 203 | } 204 | arr.as_any() 205 | .downcast_ref::() 206 | .unwrap() 207 | .value_as_datetime(idx) 208 | } 209 | 210 | fn get_time32_millisecond_value(arr: &Arc, idx: usize) -> Option { 211 | if arr.is_null(idx) { 212 | return None; 213 | } 214 | arr.as_any() 215 | .downcast_ref::() 216 | .unwrap() 217 | .value_as_datetime(idx) 218 | } 219 | 220 | fn get_time64_microsecond_value(arr: &Arc, idx: usize) -> Option { 221 | if arr.is_null(idx) { 222 | return None; 223 | } 224 | arr.as_any() 225 | .downcast_ref::() 226 | .unwrap() 227 | .value_as_datetime(idx) 228 | } 229 | fn get_time64_nanosecond_value(arr: &Arc, idx: usize) -> Option { 230 | if arr.is_null(idx) { 231 | return None; 232 | } 233 | arr.as_any() 234 | .downcast_ref::() 235 | .unwrap() 236 | .value_as_datetime(idx) 237 | } 238 | 239 | fn get_numeric_128_value( 240 | arr: &Arc, 241 | idx: usize, 242 | scale: u32, 243 | ) -> PgWireResult> { 244 | if arr.is_null(idx) { 245 | return Ok(None); 246 | } 247 | 248 | let array = arr.as_any().downcast_ref::().unwrap(); 249 | let value = array.value(idx); 250 | Decimal::try_from_i128_with_scale(value, scale) 251 | .map_err(|e| { 252 | let message = match e { 253 | rust_decimal::Error::ExceedsMaximumPossibleValue => { 254 | "Exceeds maximum possible value" 255 | } 256 | rust_decimal::Error::LessThanMinimumPossibleValue => { 257 | "Less than minimum possible value" 258 | } 259 | rust_decimal::Error::ScaleExceedsMaximumPrecision(_) => { 260 | "Scale exceeds maximum precision" 261 | } 262 | _ => unreachable!(), 263 | }; 264 | PgWireError::UserError(Box::new(ErrorInfo::new( 265 | "ERROR".to_owned(), 266 | "XX000".to_owned(), 267 | message.to_owned(), 268 | ))) 269 | }) 270 | .map(Some) 271 | } 272 | 273 | fn encode_value( 274 | encoder: &mut T, 275 | arr: &Arc, 276 | idx: usize, 277 | type_: &Type, 278 | format: FieldFormat, 279 | ) -> PgWireResult<()> { 280 | match arr.data_type() { 281 | DataType::Null => encoder.encode_field_with_type_and_format(&None::, type_, format)?, 282 | DataType::Boolean => { 283 | encoder.encode_field_with_type_and_format(&get_bool_value(arr, idx), type_, format)? 284 | } 285 | DataType::Int8 => { 286 | encoder.encode_field_with_type_and_format(&get_i8_value(arr, idx), type_, format)? 287 | } 288 | DataType::Int16 => { 289 | encoder.encode_field_with_type_and_format(&get_i16_value(arr, idx), type_, format)? 290 | } 291 | DataType::Int32 => { 292 | encoder.encode_field_with_type_and_format(&get_i32_value(arr, idx), type_, format)? 293 | } 294 | DataType::Int64 => { 295 | encoder.encode_field_with_type_and_format(&get_i64_value(arr, idx), type_, format)? 296 | } 297 | DataType::UInt8 => encoder.encode_field_with_type_and_format( 298 | &(get_u8_value(arr, idx).map(|x| x as i8)), 299 | type_, 300 | format, 301 | )?, 302 | DataType::UInt16 => encoder.encode_field_with_type_and_format( 303 | &(get_u16_value(arr, idx).map(|x| x as i16)), 304 | type_, 305 | format, 306 | )?, 307 | DataType::UInt32 => { 308 | encoder.encode_field_with_type_and_format(&get_u32_value(arr, idx), type_, format)? 309 | } 310 | DataType::UInt64 => encoder.encode_field_with_type_and_format( 311 | &(get_u64_value(arr, idx).map(|x| x as i64)), 312 | type_, 313 | format, 314 | )?, 315 | DataType::Float32 => { 316 | encoder.encode_field_with_type_and_format(&get_f32_value(arr, idx), type_, format)? 317 | } 318 | DataType::Float64 => { 319 | encoder.encode_field_with_type_and_format(&get_f64_value(arr, idx), type_, format)? 320 | } 321 | DataType::Decimal128(_, s) => encoder.encode_field_with_type_and_format( 322 | &get_numeric_128_value(arr, idx, *s as u32)?, 323 | type_, 324 | format, 325 | )?, 326 | DataType::Utf8 => { 327 | encoder.encode_field_with_type_and_format(&get_utf8_value(arr, idx), type_, format)? 328 | } 329 | DataType::Utf8View => encoder.encode_field_with_type_and_format( 330 | &get_utf8_view_value(arr, idx), 331 | type_, 332 | format, 333 | )?, 334 | DataType::LargeUtf8 => encoder.encode_field_with_type_and_format( 335 | &get_large_utf8_value(arr, idx), 336 | type_, 337 | format, 338 | )?, 339 | DataType::Binary => { 340 | encoder.encode_field_with_type_and_format(&get_binary_value(arr, idx), type_, format)? 341 | } 342 | DataType::LargeBinary => encoder.encode_field_with_type_and_format( 343 | &get_large_binary_value(arr, idx), 344 | type_, 345 | format, 346 | )?, 347 | DataType::Date32 => { 348 | encoder.encode_field_with_type_and_format(&get_date32_value(arr, idx), type_, format)? 349 | } 350 | DataType::Date64 => { 351 | encoder.encode_field_with_type_and_format(&get_date64_value(arr, idx), type_, format)? 352 | } 353 | DataType::Time32(unit) => match unit { 354 | TimeUnit::Second => encoder.encode_field_with_type_and_format( 355 | &get_time32_second_value(arr, idx), 356 | type_, 357 | format, 358 | )?, 359 | TimeUnit::Millisecond => encoder.encode_field_with_type_and_format( 360 | &get_time32_millisecond_value(arr, idx), 361 | type_, 362 | format, 363 | )?, 364 | _ => {} 365 | }, 366 | DataType::Time64(unit) => match unit { 367 | TimeUnit::Microsecond => encoder.encode_field_with_type_and_format( 368 | &get_time64_microsecond_value(arr, idx), 369 | type_, 370 | format, 371 | )?, 372 | TimeUnit::Nanosecond => encoder.encode_field_with_type_and_format( 373 | &get_time64_nanosecond_value(arr, idx), 374 | type_, 375 | format, 376 | )?, 377 | _ => {} 378 | }, 379 | DataType::Timestamp(unit, timezone) => match unit { 380 | TimeUnit::Second => { 381 | if arr.is_null(idx) { 382 | return encoder.encode_field_with_type_and_format( 383 | &None::, 384 | type_, 385 | format, 386 | ); 387 | } 388 | let ts_array = arr.as_any().downcast_ref::().unwrap(); 389 | if let Some(tz) = timezone { 390 | let tz = Tz::from_str(tz.as_ref()) 391 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 392 | let value = ts_array 393 | .value_as_datetime_with_tz(idx, tz) 394 | .map(|d| d.fixed_offset()); 395 | encoder.encode_field_with_type_and_format(&value, type_, format)?; 396 | } else { 397 | let value = ts_array.value_as_datetime(idx); 398 | encoder.encode_field_with_type_and_format(&value, type_, format)?; 399 | } 400 | } 401 | TimeUnit::Millisecond => { 402 | if arr.is_null(idx) { 403 | return encoder.encode_field_with_type_and_format( 404 | &None::, 405 | type_, 406 | format, 407 | ); 408 | } 409 | let ts_array = arr 410 | .as_any() 411 | .downcast_ref::() 412 | .unwrap(); 413 | if let Some(tz) = timezone { 414 | let tz = Tz::from_str(tz.as_ref()) 415 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 416 | let value = ts_array 417 | .value_as_datetime_with_tz(idx, tz) 418 | .map(|d| d.fixed_offset()); 419 | encoder.encode_field_with_type_and_format(&value, type_, format)?; 420 | } else { 421 | let value = ts_array.value_as_datetime(idx); 422 | encoder.encode_field_with_type_and_format(&value, type_, format)?; 423 | } 424 | } 425 | TimeUnit::Microsecond => { 426 | if arr.is_null(idx) { 427 | return encoder.encode_field_with_type_and_format( 428 | &None::, 429 | type_, 430 | format, 431 | ); 432 | } 433 | let ts_array = arr 434 | .as_any() 435 | .downcast_ref::() 436 | .unwrap(); 437 | if let Some(tz) = timezone { 438 | let tz = Tz::from_str(tz.as_ref()) 439 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 440 | let value = ts_array 441 | .value_as_datetime_with_tz(idx, tz) 442 | .map(|d| d.fixed_offset()); 443 | encoder.encode_field_with_type_and_format(&value, type_, format)?; 444 | } else { 445 | let value = ts_array.value_as_datetime(idx); 446 | encoder.encode_field_with_type_and_format(&value, type_, format)?; 447 | } 448 | } 449 | TimeUnit::Nanosecond => { 450 | if arr.is_null(idx) { 451 | return encoder.encode_field_with_type_and_format( 452 | &None::, 453 | type_, 454 | format, 455 | ); 456 | } 457 | let ts_array = arr 458 | .as_any() 459 | .downcast_ref::() 460 | .unwrap(); 461 | if let Some(tz) = timezone { 462 | let tz = Tz::from_str(tz.as_ref()) 463 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 464 | let value = ts_array 465 | .value_as_datetime_with_tz(idx, tz) 466 | .map(|d| d.fixed_offset()); 467 | encoder.encode_field_with_type_and_format(&value, type_, format)?; 468 | } else { 469 | let value = ts_array.value_as_datetime(idx); 470 | encoder.encode_field_with_type_and_format(&value, type_, format)?; 471 | } 472 | } 473 | }, 474 | DataType::List(_) | DataType::FixedSizeList(_, _) | DataType::LargeList(_) => { 475 | if arr.is_null(idx) { 476 | return encoder.encode_field_with_type_and_format(&None::<&[i8]>, type_, format); 477 | } 478 | let array = arr.as_any().downcast_ref::().unwrap().value(idx); 479 | let value = encode_list(array, type_, format)?; 480 | encoder.encode_field_with_type_and_format(&value, type_, format)? 481 | } 482 | DataType::Struct(_) => { 483 | let fields = match type_.kind() { 484 | postgres_types::Kind::Composite(fields) => fields, 485 | _ => { 486 | return Err(PgWireError::UserError(Box::new(ErrorInfo::new( 487 | "ERROR".to_owned(), 488 | "XX000".to_owned(), 489 | format!("Failed to unwrap a composite type from type {}", type_), 490 | )))) 491 | } 492 | }; 493 | let value = encode_struct(arr, idx, fields, format)?; 494 | encoder.encode_field_with_type_and_format(&value, type_, format)? 495 | } 496 | DataType::Dictionary(_, value_type) => { 497 | if arr.is_null(idx) { 498 | return encoder.encode_field_with_type_and_format(&None::, type_, format); 499 | } 500 | // Get the dictionary values, ignoring keys 501 | // We'll use Int32Type as a common key type, but we're only interested in values 502 | macro_rules! get_dict_values { 503 | ($key_type:ty) => { 504 | arr.as_any() 505 | .downcast_ref::>() 506 | .map(|dict| dict.values()) 507 | }; 508 | } 509 | 510 | // Try to extract values using different key types 511 | let values = get_dict_values!(Int8Type) 512 | .or_else(|| get_dict_values!(Int16Type)) 513 | .or_else(|| get_dict_values!(Int32Type)) 514 | .or_else(|| get_dict_values!(Int64Type)) 515 | .or_else(|| get_dict_values!(UInt8Type)) 516 | .or_else(|| get_dict_values!(UInt16Type)) 517 | .or_else(|| get_dict_values!(UInt32Type)) 518 | .or_else(|| get_dict_values!(UInt64Type)) 519 | .ok_or_else(|| { 520 | PgWireError::UserError(Box::new(ErrorInfo::new( 521 | "ERROR".to_owned(), 522 | "XX000".to_owned(), 523 | format!( 524 | "Unsupported dictionary key type for value type {}", 525 | value_type 526 | ), 527 | ))) 528 | })?; 529 | 530 | // If the dictionary has only one value, treat it as a primitive 531 | if values.len() == 1 { 532 | encode_value(encoder, values, 0, type_, format)? 533 | } else { 534 | // Otherwise, use value directly indexed by values array 535 | encode_value(encoder, values, idx, type_, format)? 536 | } 537 | } 538 | _ => { 539 | return Err(PgWireError::UserError(Box::new(ErrorInfo::new( 540 | "ERROR".to_owned(), 541 | "XX000".to_owned(), 542 | format!( 543 | "Unsupported Datatype {} and array {:?}", 544 | arr.data_type(), 545 | &arr 546 | ), 547 | )))) 548 | } 549 | } 550 | 551 | Ok(()) 552 | } 553 | -------------------------------------------------------------------------------- /datafusion-postgres/src/encoder/row_encoder.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use datafusion::arrow::array::RecordBatch; 4 | use pgwire::{ 5 | api::results::{DataRowEncoder, FieldInfo}, 6 | error::PgWireResult, 7 | messages::data::DataRow, 8 | }; 9 | 10 | use super::encode_value; 11 | 12 | pub struct RowEncoder { 13 | rb: RecordBatch, 14 | curr_idx: usize, 15 | fields: Arc>, 16 | } 17 | 18 | impl RowEncoder { 19 | pub fn new(rb: RecordBatch, fields: Arc>) -> Self { 20 | assert_eq!(rb.num_columns(), fields.len()); 21 | Self { 22 | rb, 23 | fields, 24 | curr_idx: 0, 25 | } 26 | } 27 | 28 | pub fn next_row(&mut self) -> Option> { 29 | if self.curr_idx == self.rb.num_rows() { 30 | return None; 31 | } 32 | let mut encoder = DataRowEncoder::new(self.fields.clone()); 33 | for col in 0..self.rb.num_columns() { 34 | let array = self.rb.column(col); 35 | let field = &self.fields[col]; 36 | let type_ = field.datatype(); 37 | let format = field.format(); 38 | encode_value(&mut encoder, array, self.curr_idx, type_, format).unwrap(); 39 | } 40 | self.curr_idx += 1; 41 | Some(encoder.finish()) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /datafusion-postgres/src/encoder/struct_encoder.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, sync::Arc}; 2 | 3 | use bytes::{BufMut, BytesMut}; 4 | use datafusion::arrow::array::{Array, StructArray}; 5 | use pgwire::{ 6 | api::results::FieldFormat, 7 | error::PgWireResult, 8 | types::{ToSqlText, QUOTE_CHECK, QUOTE_ESCAPE}, 9 | }; 10 | use postgres_types::{Field, IsNull, ToSql, Type}; 11 | 12 | use super::{encode_value, EncodedValue}; 13 | 14 | pub fn encode_struct( 15 | arr: &Arc, 16 | idx: usize, 17 | fields: &[Field], 18 | format: FieldFormat, 19 | ) -> Result, Box> { 20 | let arr = arr.as_any().downcast_ref::().unwrap(); 21 | if arr.is_null(idx) { 22 | return Ok(None); 23 | } 24 | let mut row_encoder = StructEncoder::new(fields.len()); 25 | for (i, arr) in arr.columns().iter().enumerate() { 26 | let field = &fields[i]; 27 | let type_ = field.type_(); 28 | encode_value(&mut row_encoder, arr, idx, type_, format).unwrap(); 29 | } 30 | Ok(Some(EncodedValue { 31 | bytes: row_encoder.row_buffer, 32 | })) 33 | } 34 | 35 | struct StructEncoder { 36 | num_cols: usize, 37 | curr_col: usize, 38 | row_buffer: BytesMut, 39 | } 40 | 41 | impl StructEncoder { 42 | fn new(num_cols: usize) -> Self { 43 | Self { 44 | num_cols, 45 | curr_col: 0, 46 | row_buffer: BytesMut::new(), 47 | } 48 | } 49 | } 50 | 51 | impl super::Encoder for StructEncoder { 52 | fn encode_field_with_type_and_format( 53 | &mut self, 54 | value: &T, 55 | data_type: &Type, 56 | format: FieldFormat, 57 | ) -> PgWireResult<()> 58 | where 59 | T: ToSql + ToSqlText + Sized, 60 | { 61 | if format == FieldFormat::Text { 62 | if self.curr_col == 0 { 63 | self.row_buffer.put_slice(b"("); 64 | } 65 | // encode value in an intermediate buf 66 | let mut buf = BytesMut::new(); 67 | value.to_sql_text(data_type, &mut buf)?; 68 | let encoded_value_as_str = String::from_utf8_lossy(&buf); 69 | if QUOTE_CHECK.is_match(&encoded_value_as_str) { 70 | self.row_buffer.put_u8(b'"'); 71 | self.row_buffer.put_slice( 72 | QUOTE_ESCAPE 73 | .replace_all(&encoded_value_as_str, r#"\$1"#) 74 | .as_bytes(), 75 | ); 76 | self.row_buffer.put_u8(b'"'); 77 | } else { 78 | self.row_buffer.put_slice(&buf); 79 | } 80 | if self.curr_col == self.num_cols - 1 { 81 | self.row_buffer.put_slice(b")"); 82 | } else { 83 | self.row_buffer.put_slice(b","); 84 | } 85 | } else { 86 | if self.curr_col == 0 && format == FieldFormat::Binary { 87 | // Place Number of fields 88 | self.row_buffer.put_i32(self.num_cols as i32); 89 | } 90 | 91 | self.row_buffer.put_u32(data_type.oid()); 92 | // remember the position of the 4-byte length field 93 | let prev_index = self.row_buffer.len(); 94 | // write value length as -1 ahead of time 95 | self.row_buffer.put_i32(-1); 96 | let is_null = value.to_sql(data_type, &mut self.row_buffer)?; 97 | if let IsNull::No = is_null { 98 | let value_length = self.row_buffer.len() - prev_index - 4; 99 | let mut length_bytes = &mut self.row_buffer[prev_index..(prev_index + 4)]; 100 | length_bytes.put_i32(value_length as i32); 101 | } 102 | } 103 | self.curr_col += 1; 104 | Ok(()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /datafusion-postgres/src/handlers.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | 4 | use arrow::datatypes::DataType; 5 | use async_trait::async_trait; 6 | use datafusion::logical_expr::LogicalPlan; 7 | use datafusion::prelude::*; 8 | use pgwire::api::auth::noop::NoopStartupHandler; 9 | use pgwire::api::copy::NoopCopyHandler; 10 | use pgwire::api::portal::{Format, Portal}; 11 | use pgwire::api::query::{ExtendedQueryHandler, SimpleQueryHandler}; 12 | use pgwire::api::results::{ 13 | DescribePortalResponse, DescribeStatementResponse, FieldFormat, FieldInfo, QueryResponse, 14 | Response, Tag, 15 | }; 16 | use pgwire::api::stmt::QueryParser; 17 | use pgwire::api::stmt::StoredStatement; 18 | use pgwire::api::{ClientInfo, NoopErrorHandler, PgWireServerHandlers, Type}; 19 | use tokio::sync::Mutex; 20 | 21 | use crate::datatypes; 22 | use crate::information_schema::{columns_df, schemata_df, tables_df}; 23 | use pgwire::error::{PgWireError, PgWireResult}; 24 | 25 | pub struct HandlerFactory(pub Arc); 26 | 27 | impl NoopStartupHandler for DfSessionService {} 28 | 29 | impl PgWireServerHandlers for HandlerFactory { 30 | type StartupHandler = DfSessionService; 31 | type SimpleQueryHandler = DfSessionService; 32 | type ExtendedQueryHandler = DfSessionService; 33 | type CopyHandler = NoopCopyHandler; 34 | type ErrorHandler = NoopErrorHandler; 35 | 36 | fn simple_query_handler(&self) -> Arc { 37 | self.0.clone() 38 | } 39 | 40 | fn extended_query_handler(&self) -> Arc { 41 | self.0.clone() 42 | } 43 | 44 | fn startup_handler(&self) -> Arc { 45 | self.0.clone() 46 | } 47 | 48 | fn copy_handler(&self) -> Arc { 49 | Arc::new(NoopCopyHandler) 50 | } 51 | 52 | fn error_handler(&self) -> Arc { 53 | Arc::new(NoopErrorHandler) 54 | } 55 | } 56 | 57 | pub struct DfSessionService { 58 | session_context: Arc, 59 | parser: Arc, 60 | timezone: Arc>, 61 | catalog_name: String, 62 | } 63 | 64 | impl DfSessionService { 65 | pub fn new(session_context: SessionContext, catalog_name: Option) -> DfSessionService { 66 | let session_context = Arc::new(session_context); 67 | let parser = Arc::new(Parser { 68 | session_context: session_context.clone(), 69 | }); 70 | let catalog_name = catalog_name.unwrap_or_else(|| { 71 | session_context 72 | .catalog_names() 73 | .first() 74 | .cloned() 75 | .unwrap_or_else(|| "datafusion".to_string()) 76 | }); 77 | DfSessionService { 78 | session_context, 79 | parser, 80 | timezone: Arc::new(Mutex::new("UTC".to_string())), 81 | catalog_name, 82 | } 83 | } 84 | 85 | fn mock_show_response<'a>(name: &str, value: &str) -> PgWireResult> { 86 | let fields = vec![FieldInfo::new( 87 | name.to_string(), 88 | None, 89 | None, 90 | Type::VARCHAR, 91 | FieldFormat::Text, 92 | )]; 93 | 94 | let row = { 95 | let mut encoder = pgwire::api::results::DataRowEncoder::new(Arc::new(fields.clone())); 96 | encoder.encode_field(&Some(value))?; 97 | encoder.finish() 98 | }; 99 | 100 | let row_stream = futures::stream::once(async move { row }); 101 | Ok(QueryResponse::new(Arc::new(fields), Box::pin(row_stream))) 102 | } 103 | 104 | // Mock pg_namespace response 105 | async fn mock_pg_namespace<'a>(&self) -> PgWireResult> { 106 | let fields = vec![FieldInfo::new( 107 | "nspname".to_string(), 108 | None, 109 | None, 110 | Type::VARCHAR, 111 | FieldFormat::Text, 112 | )]; 113 | 114 | let row = { 115 | let mut encoder = pgwire::api::results::DataRowEncoder::new(Arc::new(fields.clone())); 116 | encoder.encode_field(&Some(&self.catalog_name))?; // Return catalog_name as a schema 117 | encoder.finish() 118 | }; 119 | 120 | let row_stream = futures::stream::once(async move { row }); 121 | Ok(QueryResponse::new(Arc::new(fields), Box::pin(row_stream))) 122 | } 123 | } 124 | 125 | #[async_trait] 126 | impl SimpleQueryHandler for DfSessionService { 127 | async fn do_query<'a, C>( 128 | &self, 129 | _client: &mut C, 130 | query: &'a str, 131 | ) -> PgWireResult>> 132 | where 133 | C: ClientInfo + Unpin + Send + Sync, 134 | { 135 | let query_lower = query.to_lowercase().trim().to_string(); 136 | log::debug!("Received query: {}", query); // Log the query for debugging 137 | 138 | if query_lower.starts_with("set time zone") { 139 | let parts: Vec<&str> = query_lower.split_whitespace().collect(); 140 | if parts.len() >= 4 { 141 | let tz = parts[3].trim_matches('"'); 142 | let mut timezone = self.timezone.lock().await; 143 | *timezone = tz.to_string(); 144 | return Ok(vec![Response::Execution(Tag::new("SET"))]); 145 | } 146 | return Err(PgWireError::UserError(Box::new( 147 | pgwire::error::ErrorInfo::new( 148 | "ERROR".to_string(), 149 | "42601".to_string(), 150 | "Invalid SET TIME ZONE syntax".to_string(), 151 | ), 152 | ))); 153 | } 154 | 155 | if query_lower.starts_with("show ") { 156 | match query_lower.as_str() { 157 | "show time zone" => { 158 | let timezone = self.timezone.lock().await.clone(); 159 | let resp = Self::mock_show_response("TimeZone", &timezone)?; 160 | return Ok(vec![Response::Query(resp)]); 161 | } 162 | "show server_version" => { 163 | let resp = Self::mock_show_response("server_version", "15.0 (DataFusion)")?; 164 | return Ok(vec![Response::Query(resp)]); 165 | } 166 | "show transaction_isolation" => { 167 | let resp = 168 | Self::mock_show_response("transaction_isolation", "read uncommitted")?; 169 | return Ok(vec![Response::Query(resp)]); 170 | } 171 | "show catalogs" => { 172 | let catalogs = self.session_context.catalog_names(); 173 | let value = catalogs.join(", "); 174 | let resp = Self::mock_show_response("Catalogs", &value)?; 175 | return Ok(vec![Response::Query(resp)]); 176 | } 177 | "show search_path" => { 178 | let resp = Self::mock_show_response("search_path", &self.catalog_name)?; 179 | return Ok(vec![Response::Query(resp)]); 180 | } 181 | _ => { 182 | return Err(PgWireError::UserError(Box::new( 183 | pgwire::error::ErrorInfo::new( 184 | "ERROR".to_string(), 185 | "42704".to_string(), 186 | format!("Unrecognized SHOW command: {}", query), 187 | ), 188 | ))); 189 | } 190 | } 191 | } 192 | 193 | if query_lower.contains("information_schema.schemata") { 194 | let df = schemata_df(&self.session_context) 195 | .await 196 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 197 | let resp = datatypes::encode_dataframe(df, &Format::UnifiedText).await?; 198 | return Ok(vec![Response::Query(resp)]); 199 | } else if query_lower.contains("information_schema.tables") { 200 | let df = tables_df(&self.session_context) 201 | .await 202 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 203 | let resp = datatypes::encode_dataframe(df, &Format::UnifiedText).await?; 204 | return Ok(vec![Response::Query(resp)]); 205 | } else if query_lower.contains("information_schema.columns") { 206 | let df = columns_df(&self.session_context) 207 | .await 208 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 209 | let resp = datatypes::encode_dataframe(df, &Format::UnifiedText).await?; 210 | return Ok(vec![Response::Query(resp)]); 211 | } 212 | 213 | // Handle pg_catalog.pg_namespace for pgcli compatibility 214 | if query_lower.contains("pg_catalog.pg_namespace") { 215 | let resp = self.mock_pg_namespace().await?; 216 | return Ok(vec![Response::Query(resp)]); 217 | } 218 | 219 | let df = self 220 | .session_context 221 | .sql(query) 222 | .await 223 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 224 | 225 | if query_lower.starts_with("insert into") { 226 | // For INSERT queries, we need to execute the query to get the row count 227 | // and return an Execution response with the proper tag 228 | let result = df 229 | .clone() 230 | .collect() 231 | .await 232 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 233 | 234 | // Extract count field from the first batch 235 | let rows_affected = result 236 | .first() 237 | .and_then(|batch| batch.column_by_name("count")) 238 | .and_then(|col| { 239 | col.as_any() 240 | .downcast_ref::() 241 | }) 242 | .map_or(0, |array| array.value(0) as usize); 243 | 244 | // Create INSERT tag with the affected row count 245 | let tag = Tag::new("INSERT").with_oid(0).with_rows(rows_affected); 246 | Ok(vec![Response::Execution(tag)]) 247 | } else { 248 | // For non-INSERT queries, return a regular Query response 249 | let resp = datatypes::encode_dataframe(df, &Format::UnifiedText).await?; 250 | Ok(vec![Response::Query(resp)]) 251 | } 252 | } 253 | } 254 | 255 | #[async_trait] 256 | impl ExtendedQueryHandler for DfSessionService { 257 | type Statement = LogicalPlan; 258 | type QueryParser = Parser; 259 | 260 | fn query_parser(&self) -> Arc { 261 | self.parser.clone() 262 | } 263 | 264 | async fn do_describe_statement( 265 | &self, 266 | _client: &mut C, 267 | target: &StoredStatement, 268 | ) -> PgWireResult 269 | where 270 | C: ClientInfo + Unpin + Send + Sync, 271 | { 272 | let plan = &target.statement; 273 | let schema = plan.schema(); 274 | let fields = datatypes::df_schema_to_pg_fields(schema.as_ref(), &Format::UnifiedBinary)?; 275 | let params = plan 276 | .get_parameter_types() 277 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 278 | 279 | let mut param_types = Vec::with_capacity(params.len()); 280 | for param_type in ordered_param_types(¶ms).iter() { 281 | // Fixed: Use ¶ms 282 | if let Some(datatype) = param_type { 283 | let pgtype = datatypes::into_pg_type(datatype)?; 284 | param_types.push(pgtype); 285 | } else { 286 | param_types.push(Type::UNKNOWN); 287 | } 288 | } 289 | 290 | Ok(DescribeStatementResponse::new(param_types, fields)) 291 | } 292 | 293 | async fn do_describe_portal( 294 | &self, 295 | _client: &mut C, 296 | target: &Portal, 297 | ) -> PgWireResult 298 | where 299 | C: ClientInfo + Unpin + Send + Sync, 300 | { 301 | let plan = &target.statement.statement; 302 | let format = &target.result_column_format; 303 | let schema = plan.schema(); 304 | let fields = datatypes::df_schema_to_pg_fields(schema.as_ref(), format)?; 305 | 306 | Ok(DescribePortalResponse::new(fields)) 307 | } 308 | 309 | async fn do_query<'a, C>( 310 | &self, 311 | _client: &mut C, 312 | portal: &'a Portal, 313 | _max_rows: usize, 314 | ) -> PgWireResult> 315 | where 316 | C: ClientInfo + Unpin + Send + Sync, 317 | { 318 | let query = portal 319 | .statement 320 | .statement 321 | .to_string() 322 | .to_lowercase() 323 | .trim() 324 | .to_string(); 325 | log::debug!("Received extended query: {}", query); // Log for debugging 326 | 327 | if query.starts_with("show ") { 328 | match query.as_str() { 329 | "show time zone" => { 330 | let timezone = self.timezone.lock().await.clone(); 331 | let resp = Self::mock_show_response("TimeZone", &timezone)?; 332 | return Ok(Response::Query(resp)); 333 | } 334 | "show server_version" => { 335 | let resp = Self::mock_show_response("server_version", "15.0 (DataFusion)")?; 336 | return Ok(Response::Query(resp)); 337 | } 338 | "show transaction_isolation" => { 339 | let resp = 340 | Self::mock_show_response("transaction_isolation", "read uncommitted")?; 341 | return Ok(Response::Query(resp)); 342 | } 343 | "show catalogs" => { 344 | let catalogs = self.session_context.catalog_names(); 345 | let value = catalogs.join(", "); 346 | let resp = Self::mock_show_response("Catalogs", &value)?; 347 | return Ok(Response::Query(resp)); 348 | } 349 | "show search_path" => { 350 | let resp = Self::mock_show_response("search_path", &self.catalog_name)?; 351 | return Ok(Response::Query(resp)); 352 | } 353 | _ => { 354 | return Err(PgWireError::UserError(Box::new( 355 | pgwire::error::ErrorInfo::new( 356 | "ERROR".to_string(), 357 | "42704".to_string(), 358 | format!("Unrecognized SHOW command: {}", query), 359 | ), 360 | ))); 361 | } 362 | } 363 | } 364 | 365 | if query.contains("information_schema.schemata") { 366 | let df = schemata_df(&self.session_context) 367 | .await 368 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 369 | let resp = datatypes::encode_dataframe(df, &portal.result_column_format).await?; 370 | return Ok(Response::Query(resp)); 371 | } else if query.contains("information_schema.tables") { 372 | let df = tables_df(&self.session_context) 373 | .await 374 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 375 | let resp = datatypes::encode_dataframe(df, &portal.result_column_format).await?; 376 | return Ok(Response::Query(resp)); 377 | } else if query.contains("information_schema.columns") { 378 | let df = columns_df(&self.session_context) 379 | .await 380 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 381 | let resp = datatypes::encode_dataframe(df, &portal.result_column_format).await?; 382 | return Ok(Response::Query(resp)); 383 | } 384 | 385 | if query.contains("pg_catalog.pg_namespace") { 386 | let resp = self.mock_pg_namespace().await?; 387 | return Ok(Response::Query(resp)); 388 | } 389 | 390 | let plan = &portal.statement.statement; 391 | let param_types = plan 392 | .get_parameter_types() 393 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 394 | let param_values = 395 | datatypes::deserialize_parameters(portal, &ordered_param_types(¶m_types))?; // Fixed: Use ¶m_types 396 | let plan = plan 397 | .clone() 398 | .replace_params_with_values(¶m_values) 399 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; // Fixed: Use ¶m_values 400 | let dataframe = self 401 | .session_context 402 | .execute_logical_plan(plan) 403 | .await 404 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 405 | let resp = datatypes::encode_dataframe(dataframe, &portal.result_column_format).await?; 406 | Ok(Response::Query(resp)) 407 | } 408 | } 409 | 410 | pub struct Parser { 411 | session_context: Arc, 412 | } 413 | 414 | #[async_trait] 415 | impl QueryParser for Parser { 416 | type Statement = LogicalPlan; 417 | 418 | async fn parse_sql(&self, sql: &str, _types: &[Type]) -> PgWireResult { 419 | let context = &self.session_context; 420 | let state = context.state(); 421 | let logical_plan = state 422 | .create_logical_plan(sql) 423 | .await 424 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 425 | let optimised = state 426 | .optimize(&logical_plan) 427 | .map_err(|e| PgWireError::ApiError(Box::new(e)))?; 428 | Ok(optimised) 429 | } 430 | } 431 | 432 | fn ordered_param_types(types: &HashMap>) -> Vec> { 433 | // Datafusion stores the parameters as a map. In our case, the keys will be 434 | // `$1`, `$2` etc. The values will be the parameter types. 435 | let mut types = types.iter().collect::>(); 436 | types.sort_by(|a, b| a.0.cmp(b.0)); 437 | types.into_iter().map(|pt| pt.1.as_ref()).collect() 438 | } 439 | -------------------------------------------------------------------------------- /datafusion-postgres/src/information_schema.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use datafusion::arrow::array::{BooleanArray, StringArray, UInt32Array}; 4 | use datafusion::arrow::datatypes::{DataType, Field, Schema}; 5 | use datafusion::arrow::record_batch::RecordBatch; 6 | use datafusion::error::DataFusionError; 7 | use datafusion::prelude::{DataFrame, SessionContext}; 8 | 9 | /// Creates a DataFrame for the `information_schema.schemata` view. 10 | pub async fn schemata_df(ctx: &SessionContext) -> Result { 11 | let catalog = ctx.catalog(ctx.catalog_names()[0].as_str()).unwrap(); // Use default catalog 12 | let schema_names: Vec = catalog.schema_names(); 13 | 14 | let schema = Arc::new(Schema::new(vec![ 15 | Field::new("catalog_name", DataType::Utf8, false), 16 | Field::new("schema_name", DataType::Utf8, false), 17 | Field::new("schema_owner", DataType::Utf8, true), // Nullable, not implemented 18 | Field::new("default_character_set_catalog", DataType::Utf8, true), 19 | Field::new("default_character_set_schema", DataType::Utf8, true), 20 | Field::new("default_character_set_name", DataType::Utf8, true), 21 | ])); 22 | 23 | let catalog_name = ctx.catalog_names()[0].clone(); // Use the first catalog name 24 | let record_batch = RecordBatch::try_new( 25 | schema.clone(), 26 | vec![ 27 | Arc::new(StringArray::from(vec![catalog_name; schema_names.len()])), 28 | Arc::new(StringArray::from(schema_names.clone())), // Clone to avoid move 29 | Arc::new(StringArray::from(vec![None::; schema_names.len()])), 30 | Arc::new(StringArray::from(vec![None::; schema_names.len()])), 31 | Arc::new(StringArray::from(vec![None::; schema_names.len()])), 32 | Arc::new(StringArray::from(vec![None::; schema_names.len()])), 33 | ], 34 | )?; 35 | 36 | ctx.read_batch(record_batch) // Use read_batch instead of read_table 37 | } 38 | 39 | /// Creates a DataFrame for the `information_schema.tables` view. 40 | pub async fn tables_df(ctx: &SessionContext) -> Result { 41 | let catalog = ctx.catalog(ctx.catalog_names()[0].as_str()).unwrap(); // Use default catalog 42 | let mut catalog_names = Vec::new(); 43 | let mut schema_names = Vec::new(); 44 | let mut table_names = Vec::new(); 45 | let mut table_types = Vec::new(); 46 | 47 | for schema_name in catalog.schema_names() { 48 | let schema = catalog.schema(&schema_name).unwrap(); 49 | for table_name in schema.table_names() { 50 | catalog_names.push(ctx.catalog_names()[0].clone()); // Use the first catalog name 51 | schema_names.push(schema_name.clone()); 52 | table_names.push(table_name.clone()); 53 | table_types.push("BASE TABLE".to_string()); // DataFusion only has base tables 54 | } 55 | } 56 | 57 | let schema = Arc::new(Schema::new(vec![ 58 | Field::new("table_catalog", DataType::Utf8, false), 59 | Field::new("table_schema", DataType::Utf8, false), 60 | Field::new("table_name", DataType::Utf8, false), 61 | Field::new("table_type", DataType::Utf8, false), 62 | ])); 63 | 64 | let record_batch = RecordBatch::try_new( 65 | schema.clone(), 66 | vec![ 67 | Arc::new(StringArray::from(catalog_names)), 68 | Arc::new(StringArray::from(schema_names)), 69 | Arc::new(StringArray::from(table_names)), 70 | Arc::new(StringArray::from(table_types)), 71 | ], 72 | )?; 73 | 74 | ctx.read_batch(record_batch) // Use read_batch instead of read_table 75 | } 76 | 77 | /// Creates a DataFrame for the `information_schema.columns` view. 78 | pub async fn columns_df(ctx: &SessionContext) -> Result { 79 | let catalog = ctx.catalog(ctx.catalog_names()[0].as_str()).unwrap(); // Use default catalog 80 | let mut catalog_names = Vec::new(); 81 | let mut schema_names = Vec::new(); 82 | let mut table_names = Vec::new(); 83 | let mut column_names = Vec::new(); 84 | let mut ordinal_positions = Vec::new(); 85 | let mut data_types = Vec::new(); 86 | let mut is_nullables = Vec::new(); 87 | 88 | for schema_name in catalog.schema_names() { 89 | let schema = catalog.schema(&schema_name).unwrap(); 90 | for table_name in schema.table_names() { 91 | let table = schema 92 | .table(&table_name) 93 | .await 94 | .unwrap_or_else(|_| panic!("Table {} not found", table_name)) 95 | .unwrap(); // Unwrap the Option after handling the Result 96 | let schema_ref = table.schema(); // Store SchemaRef in a variable 97 | let fields = schema_ref.fields(); // Borrow fields from the stored SchemaRef 98 | for (idx, field) in fields.iter().enumerate() { 99 | catalog_names.push(ctx.catalog_names()[0].clone()); // Use the first catalog name 100 | schema_names.push(schema_name.clone()); 101 | table_names.push(table_name.clone()); 102 | column_names.push(field.name().clone()); 103 | ordinal_positions.push((idx + 1) as u32); // 1-based index 104 | data_types.push(field.data_type().to_string()); 105 | is_nullables.push(field.is_nullable()); 106 | } 107 | } 108 | } 109 | 110 | let schema = Arc::new(Schema::new(vec![ 111 | Field::new("table_catalog", DataType::Utf8, false), 112 | Field::new("table_schema", DataType::Utf8, false), 113 | Field::new("table_name", DataType::Utf8, false), 114 | Field::new("column_name", DataType::Utf8, false), 115 | Field::new("ordinal_position", DataType::UInt32, false), 116 | Field::new("data_type", DataType::Utf8, false), 117 | Field::new("is_nullable", DataType::Boolean, false), 118 | ])); 119 | 120 | let record_batch = RecordBatch::try_new( 121 | schema.clone(), 122 | vec![ 123 | Arc::new(StringArray::from(catalog_names)), 124 | Arc::new(StringArray::from(schema_names)), 125 | Arc::new(StringArray::from(table_names)), 126 | Arc::new(StringArray::from(column_names)), 127 | Arc::new(UInt32Array::from(ordinal_positions)), 128 | Arc::new(StringArray::from(data_types)), 129 | Arc::new(BooleanArray::from(is_nullables)), 130 | ], 131 | )?; 132 | 133 | ctx.read_batch(record_batch) // Use read_batch instead of read_table 134 | } 135 | -------------------------------------------------------------------------------- /datafusion-postgres/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod datatypes; 2 | mod encoder; 3 | mod handlers; 4 | mod information_schema; 5 | 6 | pub use handlers::{DfSessionService, HandlerFactory, Parser}; 7 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "fenix": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ], 8 | "rust-analyzer-src": "rust-analyzer-src" 9 | }, 10 | "locked": { 11 | "lastModified": 1745735608, 12 | "narHash": "sha256-L0jzm815XBFfF2wCFmR+M1CF+beIEFj6SxlqVKF59Ec=", 13 | "owner": "nix-community", 14 | "repo": "fenix", 15 | "rev": "c39a78eba6ed2a022cc3218db90d485077101496", 16 | "type": "github" 17 | }, 18 | "original": { 19 | "owner": "nix-community", 20 | "repo": "fenix", 21 | "type": "github" 22 | } 23 | }, 24 | "flake-utils": { 25 | "inputs": { 26 | "systems": "systems" 27 | }, 28 | "locked": { 29 | "lastModified": 1731533236, 30 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 31 | "owner": "numtide", 32 | "repo": "flake-utils", 33 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 34 | "type": "github" 35 | }, 36 | "original": { 37 | "owner": "numtide", 38 | "repo": "flake-utils", 39 | "type": "github" 40 | } 41 | }, 42 | "nixpkgs": { 43 | "locked": { 44 | "lastModified": 1745487689, 45 | "narHash": "sha256-FQoi3R0NjQeBAsEOo49b5tbDPcJSMWc3QhhaIi9eddw=", 46 | "owner": "NixOS", 47 | "repo": "nixpkgs", 48 | "rev": "5630cf13cceac06cefe9fc607e8dfa8fb342dde3", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "NixOS", 53 | "ref": "nixos-24.11", 54 | "repo": "nixpkgs", 55 | "type": "github" 56 | } 57 | }, 58 | "root": { 59 | "inputs": { 60 | "fenix": "fenix", 61 | "flake-utils": "flake-utils", 62 | "nixpkgs": "nixpkgs" 63 | } 64 | }, 65 | "rust-analyzer-src": { 66 | "flake": false, 67 | "locked": { 68 | "lastModified": 1745694049, 69 | "narHash": "sha256-fxvRYH/tS7hGQeg9zCVh5RBcSWT+JGJet7RA8Ss+rC0=", 70 | "owner": "rust-lang", 71 | "repo": "rust-analyzer", 72 | "rev": "d8887c0758bbd2d5f752d5bd405d4491e90e7ed6", 73 | "type": "github" 74 | }, 75 | "original": { 76 | "owner": "rust-lang", 77 | "ref": "nightly", 78 | "repo": "rust-analyzer", 79 | "type": "github" 80 | } 81 | }, 82 | "systems": { 83 | "locked": { 84 | "lastModified": 1681028828, 85 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 86 | "owner": "nix-systems", 87 | "repo": "default", 88 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 89 | "type": "github" 90 | }, 91 | "original": { 92 | "owner": "nix-systems", 93 | "repo": "default", 94 | "type": "github" 95 | } 96 | } 97 | }, 98 | "root": "root", 99 | "version": 7 100 | } 101 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Development environment flake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; 6 | fenix = { 7 | url = "github:nix-community/fenix"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | flake-utils.url = "github:numtide/flake-utils"; 11 | }; 12 | 13 | outputs = { self, nixpkgs, fenix, flake-utils }: 14 | flake-utils.lib.eachDefaultSystem (system: 15 | let 16 | pkgs = nixpkgs.legacyPackages.${system}; 17 | pythonEnv = pkgs.python3.withPackages (ps: with ps; [ 18 | psycopg 19 | ]); 20 | buildInputs = with pkgs; [ 21 | ]; 22 | in 23 | { 24 | devShells.default = pkgs.mkShell { 25 | nativeBuildInputs = with pkgs; [ 26 | pkg-config 27 | git 28 | mold 29 | (fenix.packages.${system}.stable.withComponents [ 30 | "cargo" 31 | "clippy" 32 | "rust-src" 33 | "rustc" 34 | "rustfmt" 35 | "rust-analyzer" 36 | ]) 37 | cargo-nextest 38 | curl 39 | gnuplot ## for cargo bench 40 | pythonEnv 41 | postgresql 42 | ]; 43 | 44 | LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; 45 | }; 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /tests-integration/all_types.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunng87/datafusion-postgres/ad15769d0777b6e0abb7fd40fc610d56f2fcb017/tests-integration/all_types.parquet -------------------------------------------------------------------------------- /tests-integration/create_arrow_testfile.py: -------------------------------------------------------------------------------- 1 | import pyarrow as pa 2 | from pyarrow import StructArray, ListArray 3 | import pyarrow.parquet as pq 4 | from datetime import date, datetime 5 | 6 | base: list[pa.Field] = [ 7 | pa.field("int32", pa.int32(), nullable=True), 8 | pa.field("float64", pa.float64(), nullable=True), 9 | pa.field("string", pa.string(), nullable=True), 10 | pa.field("bool", pa.bool_(), nullable=True), 11 | pa.field("date32", pa.date32(), nullable=True), 12 | pa.field("timestamp", pa.timestamp("ms"), nullable=True), 13 | ] 14 | list_type = [ 15 | pa.field(str(inner.name) + "_list", pa.list_(inner), nullable=True) 16 | for inner in base 17 | ] 18 | struct_type = [pa.field("struct", pa.struct(base), nullable=True)] 19 | list_struct_type = [pa.field("list_struct", pa.list_(struct_type[0]), nullable=True)] 20 | fields = base + list_type + struct_type + list_struct_type 21 | schema = pa.schema(fields) 22 | 23 | base_data = [ 24 | pa.array([1, None, 2], type=pa.int32()), 25 | pa.array([1.0, None, 2.0], type=pa.float64()), 26 | pa.array(["a", None, "b"], type=pa.string()), 27 | pa.array([True, None, False], type=pa.bool_()), 28 | pa.array([date(2012, 1, 1), None, date(2012, 1, 2)], type=pa.date32()), 29 | pa.array( 30 | [datetime(2012, 1, 1), None, datetime(2012, 1, 2)], type=pa.timestamp("ms") 31 | ), 32 | ] 33 | list_data = [pa.array([x.to_pylist(), None, None]) for x in base_data] 34 | struct_data = [StructArray.from_arrays(base_data, fields=struct_type[0].type.fields)] 35 | list_struct_data = [ListArray.from_arrays(pa.array([0, 1, 2, 3]), struct_data[0])] 36 | 37 | arrays = base_data + list_data + struct_data + list_struct_data 38 | 39 | # Create a table 40 | table = pa.Table.from_arrays(arrays, schema=schema) 41 | 42 | pq.write_table(table, "all_types.parquet") 43 | -------------------------------------------------------------------------------- /tests-integration/delhiclimate.csv: -------------------------------------------------------------------------------- 1 | date,meantemp,humidity,winspeed,meanpressure 2 | 2013-01-01,10.0,84.5,0.0,1015.6666666666666 3 | 2013-01-02,7.4,92.0,2.98,1017.8 4 | 2013-01-03,7.166666666666667,87.0,4.633333333333334,1018.6666666666666 5 | 2013-01-04,8.666666666666666,71.33333333333333,1.2333333333333334,1017.1666666666666 6 | 2013-01-05,6.0,86.83333333333333,3.6999999999999997,1016.5 7 | 2013-01-06,7.0,82.8,1.48,1018.0 8 | 2013-01-07,7.0,78.6,6.3,1020.0 9 | 2013-01-08,8.857142857142858,63.714285714285715,7.142857142857143,1018.7142857142857 10 | 2013-01-09,14.0,51.25,12.5,1017.0 11 | 2013-01-10,11.0,62.0,7.3999999999999995,1015.6666666666666 12 | 2013-01-11,15.714285714285714,51.285714285714285,10.571428571428571,1016.1428571428571 13 | 2013-01-12,14.0,74.0,13.228571428571428,1015.5714285714286 14 | 2013-01-13,15.833333333333334,75.16666666666667,4.633333333333334,1013.3333333333334 15 | 2013-01-14,12.833333333333334,88.16666666666667,0.6166666666666667,1015.1666666666666 16 | 2013-01-15,14.714285714285714,71.85714285714286,0.5285714285714286,1015.8571428571429 17 | 2013-01-16,13.833333333333334,86.66666666666667,0.0,1016.6666666666666 18 | 2013-01-17,16.5,80.83333333333333,5.250000000000001,1015.8333333333334 19 | 2013-01-18,13.833333333333334,92.16666666666667,8.950000000000001,1014.5 20 | 2013-01-19,12.5,76.66666666666667,5.883333333333333,1021.6666666666666 21 | 2013-01-20,11.285714285714286,75.28571428571429,8.471428571428572,1020.2857142857143 22 | 2013-01-21,11.2,77.0,2.22,1021.0 23 | 2013-01-22,9.5,79.66666666666667,3.0833333333333335,1021.8 24 | 2013-01-23,14.0,60.166666666666664,4.016666666666667,1020.5 25 | 2013-01-24,13.833333333333334,60.666666666666664,6.166666666666667,1020.5 26 | 2013-01-25,12.25,67.0,5.55,1020.75 27 | 2013-01-26,12.666666666666666,64.16666666666667,6.800000000000001,1019.6666666666666 28 | 2013-01-27,12.857142857142858,65.57142857142857,5.557142857142857,1018.1428571428571 29 | 2013-01-28,14.833333333333334,56.0,3.6999999999999997,1017.8333333333334 30 | 2013-01-29,14.125,65.5,3.2375,1016.625 31 | 2013-01-30,14.714285714285714,70.42857142857143,1.0571428571428572,1017.8571428571429 32 | 2013-01-31,16.2,65.6,2.96,1018.4 33 | 2013-02-01,16.0,73.0,2.22,1016.0 34 | 2013-02-02,16.285714285714285,77.57142857142857,1.3285714285714287,1017.1428571428571 35 | 2013-02-03,18.0,65.57142857142857,1.8571428571428572,1015.2857142857143 36 | 2013-02-04,17.428571428571427,74.28571428571429,11.114285714285714,1014.5714285714286 37 | 2013-02-05,16.625,92.375,9.725000000000001,1016.375 38 | 2013-02-06,16.666666666666668,71.33333333333333,8.633333333333333,1018.6666666666666 39 | 2013-02-07,15.6,59.4,10.74,1018.6 40 | 2013-02-08,14.0,70.42857142857143,9.257142857142856,1017.1428571428571 41 | 2013-02-09,15.428571428571429,61.285714285714285,9.257142857142856,1016.8571428571429 42 | 2013-02-10,15.25,71.5,3.4749999999999996,1017.125 43 | 2013-02-11,15.875,70.5,5.325,1016.5 44 | 2013-02-12,15.333333333333334,70.33333333333333,7.416666666666667,1017.5 45 | 2013-02-13,16.285714285714285,70.14285714285714,6.085714285714287,1016.0 46 | 2013-02-14,17.333333333333332,63.833333333333336,4.333333333333333,1014.1666666666666 47 | 2013-02-15,19.166666666666668,65.33333333333333,10.183333333333335,1011.6666666666666 48 | 2013-02-16,14.428571428571429,92.71428571428571,8.485714285714286,1008.0 49 | 2013-02-17,13.666666666666666,90.0,0.0,1012.6666666666666 50 | 2013-02-18,15.6,78.4,12.220000000000002,1016.2 51 | 2013-02-19,15.857142857142858,82.0,5.814285714285715,1015.7142857142857 52 | 2013-02-20,17.714285714285715,74.71428571428571,5.814285714285715,1017.0 53 | 2013-02-21,20.0,67.28571428571429,6.614285714285714,1015.4285714285714 54 | 2013-02-22,20.5,65.625,10.875000000000002,1016.0 55 | 2013-02-23,17.428571428571427,74.85714285714286,9.257142857142856,1017.0 56 | 2013-02-24,16.857142857142858,78.85714285714286,7.400000000000001,1018.8571428571429 57 | 2013-02-25,16.875,72.875,4.6375,1018.25 58 | 2013-02-26,17.857142857142858,70.0,17.587500000000002,1015.1428571428571 59 | 2013-02-27,20.8,57.2,6.660000000000001,1015.2 60 | 2013-02-28,19.428571428571427,52.857142857142854,12.957142857142857,1017.4285714285714 61 | 2013-03-01,17.333333333333332,49.333333333333336,24.066666666666663,1016.3333333333334 62 | 2013-03-02,19.0,54.0,15.725,1016.25 63 | 2013-03-03,19.333333333333332,62.833333333333336,8.633333333333333,1016.1666666666666 64 | 2013-03-04,17.6,71.0,5.5600000000000005,1015.8 65 | 2013-03-05,20.875,61.875,4.1625000000000005,1016.375 66 | 2013-03-06,20.857142857142858,65.28571428571429,6.871428571428573,1015.7142857142857 67 | 2013-03-07,23.428571428571427,57.142857142857146,8.728571428571428,1015.2857142857143 68 | 2013-03-08,24.166666666666668,44.833333333333336,7.1000000000000005,1014.8333333333334 69 | 2013-03-09,25.428571428571427,49.714285714285715,5.285714285714286,1009.2857142857143 70 | 2013-03-10,23.142857142857142,57.57142857142857,4.228571428571429,1008.0 71 | 2013-03-11,24.0,66.33333333333333,0.9333333333333332,1011.1666666666666 72 | 2013-03-12,23.5,62.5,4.933333333333334,1011.5 73 | 2013-03-13,21.5,70.5,5.55,1009.0 74 | 2013-03-14,22.333333333333332,61.166666666666664,12.033333333333333,1013.1666666666666 75 | 2013-03-15,24.166666666666668,45.833333333333336,7.716666666666668,1016.1666666666666 76 | 2013-03-16,20.333333333333332,67.66666666666667,3.6999999999999997,1016.1666666666666 77 | 2013-03-17,22.666666666666668,58.666666666666664,8.95,1015.0 78 | 2013-03-18,23.428571428571427,58.142857142857146,17.457142857142856,1009.4285714285714 79 | 2013-03-19,22.5,73.66666666666667,10.483333333333333,1011.0 80 | 2013-03-20,29.166666666666668,36.333333333333336,6.800000000000001,1009.5 81 | 2013-03-21,23.833333333333332,58.5,10.5,1008.3333333333334 82 | 2013-03-22,25.25,50.25,9.7125,1006.375 83 | 2013-03-23,27.375,50.125,9.950000000000001,1007.625 84 | 2013-03-24,27.0,48.75,10.8875,1010.25 85 | 2013-03-25,23.5,45.5,15.962499999999999,1010.625 86 | 2013-03-26,24.142857142857142,44.57142857142857,12.957142857142857,1008.8571428571429 87 | 2013-03-27,21.0,62.0,1.85,1009.0 88 | 2013-03-28,22.428571428571427,62.714285714285715,7.4142857142857155,1009.5714285714286 89 | 2013-03-29,21.25,70.375,5.550000000000001,1009.75 90 | 2013-03-30,23.5,54.75,11.112499999999999,1008.625 91 | 2013-03-31,23.2,58.0,6.660000000000001,1008.6 92 | 2013-04-01,25.375,45.5,4.4,1008.5 93 | 2013-04-02,25.166666666666668,51.0,8.65,1009.5 94 | 2013-04-03,26.2,45.6,8.14,1009.0 95 | 2013-04-04,24.6,41.8,11.120000000000001,1007.8 96 | 2013-04-05,25.6,31.0,15.559999999999999,1007.0 97 | 2013-04-06,25.857142857142858,29.857142857142858,11.900000000000002,1006.1428571428571 98 | 2013-04-07,29.142857142857142,23.285714285714285,10.314285714285715,1005.0 99 | 2013-04-08,28.714285714285715,33.857142857142854,5.3,1006.0 100 | 2013-04-09,30.166666666666668,30.5,8.65,1005.3333333333334 101 | 2013-04-10,30.0,28.0,6.1,1006.7142857142857 102 | 2013-04-11,30.0,24.2,7.780000000000001,1006.4 103 | 2013-04-12,28.857142857142858,32.57142857142857,6.342857142857143,1007.5714285714286 104 | 2013-04-13,30.2,29.2,10.000000000000002,1008.4 105 | 2013-04-14,28.25,39.375,6.487499999999999,1007.0 106 | 2013-04-15,28.25,41.375,6.25,1003.875 107 | 2013-04-16,32.125,24.625,10.424999999999999,1000.0 108 | 2013-04-17,29.2,24.2,6.659999999999999,1002.2 109 | 2013-04-18,30.285714285714285,30.285714285714285,4.757142857142858,1002.8571428571429 110 | 2013-04-19,28.285714285714285,31.285714285714285,3.971428571428571,1002.5714285714286 111 | 2013-04-20,30.625,29.0,7.6375,1003.375 112 | 2013-04-21,27.666666666666668,38.666666666666664,13.883333333333333,1006.8333333333334 113 | 2013-04-22,27.375,45.375,7.650000000000001,1010.0 114 | 2013-04-23,28.625,44.125,4.625000000000001,1009.875 115 | 2013-04-24,30.285714285714285,41.714285714285715,2.1142857142857143,1008.5714285714286 116 | 2013-04-25,31.142857142857142,38.285714285714285,3.7,1007.7142857142857 117 | 2013-04-26,29.875,45.875,6.7125,1008.375 118 | 2013-04-27,31.142857142857142,31.428571428571427,13.485714285714286,1007.0 119 | 2013-04-28,30.571428571428573,28.0,12.971428571428572,1005.4285714285714 120 | 2013-04-29,32.125,26.375,7.875,1004.875 121 | 2013-04-30,31.142857142857142,32.0,7.928571428571429,1004.8571428571429 122 | 2013-05-01,31.857142857142858,15.857142857142858,12.685714285714287,1002.8333333333334 123 | 2013-05-02,29.833333333333332,22.166666666666668,11.433333333333335,1001.2 124 | 2013-05-03,28.571428571428573,31.571428571428573,9.0,999.4285714285714 125 | 2013-05-04,32.857142857142854,31.428571428571427,2.914285714285714,999.0 126 | 2013-05-05,32.625,31.125,3.0125,1000.625 127 | 2013-05-06,32.75,39.25,3.7,1001.625 128 | 2013-05-07,32.875,33.25,7.175000000000001,1002.0 129 | 2013-05-08,34.5,23.0,9.25,1001.1666666666666 130 | 2013-05-09,34.285714285714285,26.0,8.985714285714284,1000.8571428571429 131 | 2013-05-10,34.0,27.714285714285715,9.528571428571428,1000.1428571428571 132 | 2013-05-11,30.75,30.375,14.8125,999.875 133 | 2013-05-12,29.857142857142858,40.142857142857146,5.828571428571428,1003.7142857142857 134 | 2013-05-13,31.714285714285715,34.0,3.9714285714285715,1003.5714285714286 135 | 2013-05-14,32.285714285714285,34.285714285714285,4.771428571428571,1001.5714285714286 136 | 2013-05-15,33.0,33.0,3.2375000000000003,999.8571428571429 137 | 2013-05-16,33.0,34.75,7.175000000000001,998.5 138 | 2013-05-17,32.833333333333336,28.166666666666668,9.866666666666667,1000.3333333333334 139 | 2013-05-18,31.4,42.2,12.966666666666669,999.0 140 | 2013-05-19,35.333333333333336,22.333333333333332,15.749999999999998,999.6666666666666 141 | 2013-05-20,36.4,24.2,7.4,998.4 142 | 2013-05-21,36.0,19.0,11.371428571428572,998.6666666666666 143 | 2013-05-22,36.75,22.125,17.5875,998.625 144 | 2013-05-23,37.5,23.333333333333332,13.566666666666668,997.1666666666666 145 | 2013-05-24,38.42857142857143,27.428571428571427,11.385714285714286,996.4285714285714 146 | 2013-05-25,38.714285714285715,22.428571428571427,10.314285714285715,998.1428571428571 147 | 2013-05-26,37.8,21.2,10.74,998.2 148 | 2013-05-27,35.857142857142854,31.571428571428573,8.72857142857143,999.1428571428571 149 | 2013-05-28,35.333333333333336,29.0,8.333333333333334,1000.1666666666666 150 | 2013-05-29,34.142857142857146,27.857142857142858,5.557142857142858,999.4285714285714 151 | 2013-05-30,32.2,28.2,5.5600000000000005,999.6 152 | 2013-05-31,33.625,40.125,10.6375,998.7142857142857 153 | 2013-06-01,32.0,54.0,13.4375,998.75 154 | 2013-06-02,32.4,56.6,14.200000000000001,1001.0 155 | 2013-06-03,35.6,47.0,12.239999999999998,999.6 156 | 2013-06-04,35.857142857142854,42.57142857142857,5.028571428571429,999.8571428571429 157 | 2013-06-05,37.166666666666664,36.5,9.866666666666665,998.0 158 | 2013-06-06,31.285714285714285,61.714285714285715,10.042857142857144,997.2857142857143 159 | 2013-06-07,34.0,49.25,7.412500000000001,998.0 160 | 2013-06-08,34.2,57.4,13.34,997.0 161 | 2013-06-09,36.166666666666664,40.0,11.416666666666666,995.1666666666666 162 | 2013-06-10,36.625,42.75,8.100000000000001,995.125 163 | 2013-06-11,30.166666666666668,61.666666666666664,16.349999999999998,1000.0 164 | 2013-06-12,34.142857142857146,54.142857142857146,7.4142857142857155,998.0 165 | 2013-06-13,29.833333333333332,68.66666666666667,9.866666666666665,998.6666666666666 166 | 2013-06-14,30.142857142857142,68.14285714285714,7.942857142857142,997.4285714285714 167 | 2013-06-15,30.714285714285715,65.0,13.22857142857143,997.8571428571429 168 | 2013-06-16,27.0,88.85714285714286,8.485714285714286,996.4285714285714 169 | 2013-06-17,26.875,87.375,8.3375,994.75 170 | 2013-06-18,28.4,83.6,5.18,996.4 171 | 2013-06-19,29.857142857142858,72.85714285714286,4.228571428571429,999.0 172 | 2013-06-20,33.0,52.714285714285715,4.771428571428571,997.1666666666666 173 | 2013-06-21,34.833333333333336,47.333333333333336,5.866666666666667,995.3333333333334 174 | 2013-06-22,35.6,39.4,10.74,996.2 175 | 2013-06-23,35.166666666666664,53.0,8.350000000000001,995.6666666666666 176 | 2013-06-24,33.142857142857146,55.0,13.22857142857143,996.2857142857143 177 | 2013-06-25,30.571428571428573,70.42857142857143,11.114285714285716,999.1666666666666 178 | 2013-06-26,30.666666666666668,71.5,6.483333333333333,999.0 179 | 2013-06-27,31.428571428571427,61.857142857142854,7.957142857142856,996.7142857142857 180 | 2013-06-28,31.5,64.25,8.8125,996.0 181 | 2013-06-29,33.25,57.0,13.875,995.25 182 | 2013-06-30,32.833333333333336,52.166666666666664,10.5,997.1666666666666 183 | 2013-07-01,33.857142857142854,54.0,8.728571428571428,996.4285714285714 184 | 2013-07-02,33.142857142857146,58.285714285714285,9.799999999999999,996.0 185 | 2013-07-03,31.571428571428573,70.0,6.357142857142857,997.5714285714286 186 | 2013-07-04,32.375,67.5,9.25,997.0 187 | 2013-07-05,32.8,62.2,12.959999999999999,997.8 188 | 2013-07-06,31.0,73.14285714285714,11.37142857142857,1000.5714285714286 189 | 2013-07-07,31.166666666666668,77.33333333333333,7.1000000000000005,1000.1666666666666 190 | 2013-07-08,29.833333333333332,75.16666666666667,6.483333333333334,997.5 191 | 2013-07-09,29.0,78.83333333333333,8.016666666666667,996.8333333333334 192 | 2013-07-10,29.75,75.375,6.4875,995.75 193 | 2013-07-11,32.5,65.5,7.116666666666667,997.3333333333334 194 | 2013-07-12,28.875,86.625,5.562500000000001,1000.0 195 | 2013-07-13,31.75,70.25,12.0625,998.875 196 | 2013-07-14,30.75,71.625,4.8125,997.375 197 | 2013-07-15,31.75,73.25,13.537500000000001,998.375 198 | 2013-07-16,29.875,83.875,9.0,999.0 199 | 2013-07-17,31.125,78.375,8.65,998.0 200 | 2013-07-18,31.75,76.0,5.550000000000001,995.0 201 | 2013-07-19,30.5,73.5,9.266666666666666,995.3333333333334 202 | 2013-07-20,26.833333333333332,90.66666666666667,4.933333333333333,999.1666666666666 203 | 2013-07-21,28.2,88.0,2.2399999999999998,996.4 204 | 2013-07-22,29.5,80.33333333333333,4.333333333333333,996.8333333333334 205 | 2013-07-23,31.857142857142858,70.71428571428571,3.9714285714285715,998.0 206 | 2013-07-24,29.714285714285715,84.14285714285714,3.971428571428571,997.1428571428571 207 | 2013-07-25,28.333333333333332,84.5,8.016666666666667,995.1666666666666 208 | 2013-07-26,30.0,73.85714285714286,5.571428571428572,995.4285714285714 209 | 2013-07-27,30.142857142857142,79.0,4.228571428571429,997.2857142857143 210 | 2013-07-28,32.285714285714285,67.42857142857143,6.085714285714286,998.1428571428571 211 | 2013-07-29,31.0,77.42857142857143,4.771428571428571,997.1428571428571 212 | 2013-07-30,30.5,75.66666666666667,8.016666666666667,996.5 213 | 2013-07-31,28.833333333333332,78.5,9.866666666666665,996.6666666666666 214 | 2013-08-01,30.0,73.5,5.550000000000001,998.0 215 | 2013-08-02,31.571428571428573,66.42857142857143,11.114285714285716,999.4285714285714 216 | 2013-08-03,32.5,62.333333333333336,10.816666666666665,1000.8333333333334 217 | 2013-08-04,32.5,62.5,5.1,993.25 218 | 2013-08-05,29.5,77.0,6.783333333333334,1001.0 219 | 2013-08-06,27.166666666666668,90.66666666666667,4.016666666666667,1002.1666666666666 220 | 2013-08-07,28.375,85.25,3.25,1001.875 221 | 2013-08-08,28.5,84.0,6.166666666666667,1000.5 222 | 2013-08-09,27.166666666666668,87.5,3.716666666666667,1001.0 223 | 2013-08-10,29.428571428571427,79.85714285714286,6.1000000000000005,1000.3333333333334 224 | 2013-08-11,30.0,76.83333333333333,6.800000000000001,1001.1666666666666 225 | 2013-08-12,29.5,81.5,8.350000000000001,1001.0 226 | 2013-08-13,30.75,72.0,11.125,999.25 227 | 2013-08-14,29.666666666666668,74.33333333333333,9.866666666666667,999.8333333333334 228 | 2013-08-15,27.714285714285715,85.0,8.2,1001.7142857142857 229 | 2013-08-16,26.6,92.0,4.46,1000.8 230 | 2013-08-17,27.428571428571427,91.0,14.285714285714286,999.4285714285714 231 | 2013-08-18,28.333333333333332,83.83333333333333,1.2333333333333334,1000.1666666666666 232 | 2013-08-19,28.166666666666668,84.66666666666667,5.550000000000001,1000.5 233 | 2013-08-20,27.375,88.5,5.325,999.0 234 | 2013-08-21,28.333333333333332,81.16666666666667,10.314285714285717,999.2857142857143 235 | 2013-08-22,29.166666666666668,74.83333333333333,6.483333333333334,1001.1666666666666 236 | 2013-08-23,31.285714285714285,73.14285714285714,2.642857142857143,1001.4285714285714 237 | 2013-08-24,32.0,71.25,18.525,1000.3333333333334 238 | 2013-08-25,29.0,90.75,0.925,998.75 239 | 2013-08-26,32.0,69.875,6.475,997.8571428571429 240 | 2013-08-27,32.142857142857146,57.285714285714285,17.071428571428573,999.8571428571429 241 | 2013-08-28,30.142857142857142,60.714285714285715,11.642857142857142,1003.7142857142857 242 | 2013-08-29,31.5,57.0,9.275,1003.25 243 | 2013-08-30,29.166666666666668,73.0,3.1,1003.5 244 | 2013-08-31,29.0,71.75,4.65,1003.25 245 | 2013-09-01,30.0,65.2,11.48,1003.2 246 | 2013-09-02,31.857142857142858,50.857142857142854,15.342857142857142,1003.7142857142857 247 | 2013-09-03,32.75,45.5,14.799999999999999,1001.5 248 | 2013-09-04,31.714285714285715,48.0,14.542857142857141,1002.1666666666666 249 | 2013-09-05,31.666666666666668,46.0,13.566666666666668,1001.5 250 | 2013-09-06,30.571428571428573,55.714285714285715,10.042857142857144,1003.4285714285714 251 | 2013-09-07,30.833333333333332,51.833333333333336,6.783333333333334,1004.1666666666666 252 | 2013-09-08,28.0,71.75,2.325,1007.25 253 | 2013-09-09,31.0,57.5,42.22,1007.0 254 | 2013-09-10,29.666666666666668,66.33333333333333,3.6999999999999997,1005.3333333333334 255 | 2013-09-11,30.857142857142858,61.142857142857146,3.971428571428571,1005.5714285714286 256 | 2013-09-12,31.166666666666668,60.333333333333336,2.7833333333333337,1008.1666666666666 257 | 2013-09-13,31.142857142857142,67.14285714285714,3.1714285714285717,1007.1428571428571 258 | 2013-09-14,30.5,67.0,2.7833333333333337,1003.8333333333334 259 | 2013-09-15,31.625,56.375,3.9375,1001.375 260 | 2013-09-16,29.666666666666668,60.0,7.100000000000001,1002.3333333333334 261 | 2013-09-17,29.25,56.5,3.475,1002.875 262 | 2013-09-18,29.142857142857142,58.57142857142857,5.557142857142858,1003.1428571428571 263 | 2013-09-19,29.8,61.6,2.96,1001.8 264 | 2013-09-20,28.666666666666668,70.0,9.583333333333334,1003.0 265 | 2013-09-21,25.2,89.0,5.18,1003.0 266 | 2013-09-22,28.333333333333332,79.66666666666667,3.0833333333333335,1001.6666666666666 267 | 2013-09-23,30.285714285714285,68.0,4.5,1001.0 268 | 2013-09-24,30.75,65.75,5.7875000000000005,1002.75 269 | 2013-09-25,28.571428571428573,75.0,6.614285714285715,1005.4285714285714 270 | 2013-09-26,28.2,78.8,4.08,1006.6 271 | 2013-09-27,29.0,71.25,7.4125000000000005,1005.625 272 | 2013-09-28,30.0,67.33333333333333,1.2333333333333334,1005.3333333333334 273 | 2013-09-29,28.142857142857142,79.85714285714286,5.557142857142858,1003.8571428571429 274 | 2013-09-30,26.857142857142858,84.57142857142857,4.228571428571429,1004.7142857142857 275 | 2013-10-01,28.285714285714285,68.71428571428571,6.371428571428571,1007.7142857142857 276 | 2013-10-02,29.2,61.4,7.42,1010.0 277 | 2013-10-03,28.6,59.0,7.4,1008.6 278 | 2013-10-04,24.833333333333332,84.33333333333333,6.783333333333334,1007.5 279 | 2013-10-05,28.5,69.0,2.0875,1004.125 280 | 2013-10-06,29.75,66.5,1.85,1005.25 281 | 2013-10-07,29.714285714285715,66.57142857142857,1.5857142857142859,1008.8571428571429 282 | 2013-10-08,29.2,69.4,0.0,1009.8 283 | 2013-10-09,30.833333333333332,65.5,5.566666666666667,1010.3333333333334 284 | 2013-10-10,29.428571428571427,66.28571428571429,3.9714285714285715,1010.8571428571429 285 | 2013-10-11,23.857142857142858,89.85714285714286,4.242857142857143,1010.5714285714286 286 | 2013-10-12,26.142857142857142,82.57142857142857,2.914285714285714,1008.2857142857143 287 | 2013-10-13,27.166666666666668,84.33333333333333,0.6166666666666667,1007.6666666666666 288 | 2013-10-14,27.714285714285715,74.85714285714286,5.557142857142858,1007.0 289 | 2013-10-15,25.857142857142858,76.42857142857143,3.6999999999999997,1009.1428571428571 290 | 2013-10-16,26.428571428571427,75.0,0.0,1009.0 291 | 2013-10-17,26.857142857142858,76.71428571428571,1.0571428571428572,1010.4285714285714 292 | 2013-10-18,26.333333333333332,73.16666666666667,0.0,1010.5 293 | 2013-10-19,24.571428571428573,69.57142857142857,2.1142857142857143,1011.5714285714286 294 | 2013-10-20,24.333333333333332,67.83333333333333,1.55,1010.6666666666666 295 | 2013-10-21,24.875,63.625,1.3875000000000002,1009.875 296 | 2013-10-22,27.8,53.6,0.0,1011.0 297 | 2013-10-23,25.0,67.5,1.1625,1013.625 298 | 2013-10-24,23.857142857142858,73.14285714285714,1.0571428571428572,1013.2857142857143 299 | 2013-10-25,22.8,75.8,1.86,1011.0 300 | 2013-10-26,23.0,78.2,0.74,1011.2 301 | 2013-10-27,22.875,66.875,2.5500000000000003,1010.625 302 | 2013-10-28,22.666666666666668,63.166666666666664,4.933333333333334,1010.0 303 | 2013-10-29,23.0,62.714285714285715,3.971428571428571,1011.1428571428571 304 | 2013-10-30,22.857142857142858,67.42857142857143,2.385714285714286,1012.8571428571429 305 | 2013-10-31,23.666666666666668,58.833333333333336,3.0833333333333335,1012.8333333333334 306 | 2013-11-01,23.428571428571427,69.14285714285714,1.0571428571428572,1013.2857142857143 307 | 2013-11-02,21.625,65.0,4.6375,1014.5 308 | 2013-11-03,20.625,57.875,5.5625,1016.0 309 | 2013-11-04,21.166666666666668,54.166666666666664,2.466666666666667,1017.5 310 | 2013-11-05,18.833333333333332,74.33333333333333,0.0,1017.5 311 | 2013-11-06,20.571428571428573,66.71428571428571,3.1857142857142855,1015.8571428571429 312 | 2013-11-07,20.142857142857142,73.14285714285714,4.500000000000001,1015.6666666666666 313 | 2013-11-08,18.0,82.2,19.9125,1015.625 314 | 2013-11-09,19.857142857142858,73.57142857142857,0.5285714285714286,1016.7142857142857 315 | 2013-11-10,16.833333333333332,72.0,2.466666666666667,1016.0 316 | 2013-11-11,18.857142857142858,61.857142857142854,5.814285714285715,1013.4285714285714 317 | 2013-11-12,16.571428571428573,71.71428571428571,0.7999999999999999,1013.8571428571429 318 | 2013-11-13,17.75,65.0,3.0125,1014.25 319 | 2013-11-14,17.625,63.375,3.475,1015.25 320 | 2013-11-15,17.0,67.0,0.925,1016.5 321 | 2013-11-16,16.625,69.625,0.0,1018.0 322 | 2013-11-17,15.571428571428571,67.42857142857143,2.3857142857142857,1017.7142857142857 323 | 2013-11-18,18.5,55.125,7.412500000000001,1016.875 324 | 2013-11-19,17.875,63.5,7.175,1015.5 325 | 2013-11-20,18.25,58.875,2.55,1012.75 326 | 2013-11-21,17.875,66.25,2.0875,1013.125 327 | 2013-11-22,17.625,74.875,1.1625,1015.75 328 | 2013-11-23,18.142857142857142,77.85714285714286,0.7999999999999999,1014.5714285714286 329 | 2013-11-24,19.125,58.875,5.0875,1014.5 330 | 2013-11-25,21.25,49.875,9.5,1012.875 331 | 2013-11-26,21.25,51.375,9.75,1011.75 332 | 2013-11-27,19.125,68.5,2.7875,1014.75 333 | 2013-11-28,18.625,71.875,1.85,1017.75 334 | 2013-11-29,17.75,66.625,4.8625,1016.875 335 | 2013-11-30,17.875,69.625,11.8125,1015.875 336 | 2013-12-01,18.0,65.0,1.5857142857142859,1016.1428571428571 337 | 2013-12-02,17.25,73.25,1.85,1015.875 338 | 2013-12-03,17.5,73.5,1.3875,1016.75 339 | 2013-12-04,17.142857142857142,71.85714285714286,4.228571428571429,1017.0 340 | 2013-12-05,17.142857142857142,69.0,6.885714285714286,1015.2857142857143 341 | 2013-12-06,16.125,72.375,3.7125000000000004,1014.0 342 | 2013-12-07,16.0,80.5,1.1625,1014.25 343 | 2013-12-08,16.5,83.625,0.0,1014.0 344 | 2013-12-09,16.125,79.25,2.3125,1013.5 345 | 2013-12-10,15.5,81.75,2.55,1013.75 346 | 2013-12-11,16.625,71.875,6.475000000000001,1014.125 347 | 2013-12-12,17.75,62.375,16.9,1012.5 348 | 2013-12-13,17.142857142857142,67.71428571428571,11.62857142857143,1012.0 349 | 2013-12-14,16.142857142857142,79.0,0.7999999999999999,1012.5714285714286 350 | 2013-12-15,15.5,86.25,2.55,1015.125 351 | 2013-12-16,15.25,89.5,1.1625,1015.625 352 | 2013-12-17,14.75,92.875,0.925,1014.25 353 | 2013-12-18,14.875,93.0,2.0875,1012.625 354 | 2013-12-19,16.125,89.125,3.2499999999999996,1012.875 355 | 2013-12-20,15.375,91.375,4.4125000000000005,1016.0 356 | 2013-12-21,14.75,94.0,0.4625,1017.0 357 | 2013-12-22,15.25,94.0,1.3875000000000002,1018.375 358 | 2013-12-23,14.25,88.25,9.025,1020.5 359 | 2013-12-24,13.5,84.75,5.7875000000000005,1021.375 360 | 2013-12-25,13.666666666666666,63.166666666666664,34.4875,1020.625 361 | 2013-12-26,12.125,67.625,4.4,1019.25 362 | 2013-12-27,11.875,79.875,1.1625,1018.625 363 | 2013-12-28,10.875,70.0,5.325,1019.25 364 | 2013-12-29,10.571428571428571,69.42857142857143,5.325,1018.5 365 | 2013-12-30,12.375,79.5,6.4750000000000005,1018.125 366 | 2013-12-31,14.5,89.375,4.862500000000001,1020.5 367 | 2014-01-01,13.375,89.625,7.65,1021.0 368 | 2014-01-02,11.0,78.375,8.1,1020.25 369 | 2014-01-03,12.5,74.875,5.325,1017.75 370 | 2014-01-04,12.875,88.125,1.1625,1016.25 371 | 2014-01-05,12.375,89.0,0.4625,1014.5 372 | 2014-01-06,11.428571428571429,86.28571428571429,27.3375,1017.625 373 | 2014-01-07,12.142857142857142,72.28571428571429,20.599999999999998,1017.75 374 | 2014-01-08,11.875,76.875,1.85,1017.875 375 | 2014-01-09,12.833333333333334,83.0,1.2333333333333334,1017.0 376 | 2014-01-10,12.375,85.375,0.0,1016.625 377 | 2014-01-11,12.125,82.625,6.7250000000000005,1019.625 378 | 2014-01-12,12.875,80.875,3.2500000000000004,1020.75 379 | 2014-01-13,14.0,72.375,1.1625,1019.375 380 | 2014-01-14,14.875,76.75,2.7875,1017.0 381 | 2014-01-15,12.0,93.5,0.4625,1019.75 382 | 2014-01-16,12.285714285714286,90.57142857142857,1.5857142857142859,1019.5714285714286 383 | 2014-01-17,12.0,95.75,1.1625,1017.625 384 | 2014-01-18,12.5,94.25,1.85,1020.25 385 | 2014-01-19,14.5,84.5,3.4749999999999996,1023.0 386 | 2014-01-20,14.625,83.5,4.4125000000000005,1021.375 387 | 2014-01-21,13.571428571428571,96.85714285714286,7.171428571428572,1020.5714285714286 388 | 2014-01-22,15.25,96.125,6.7124999999999995,1019.875 389 | 2014-01-23,15.625,90.125,6.7125,1020.25 390 | 2014-01-24,13.875,88.875,8.1125,1019.25 391 | 2014-01-25,11.75,95.625,8.3375,1019.75 392 | 2014-01-26,14.375,85.75,8.8125,1019.125 393 | 2014-01-27,16.625,72.375,9.950000000000001,1018.75 394 | 2014-01-28,16.625,69.875,7.4125000000000005,1016.75 395 | 2014-01-29,14.875,81.875,4.625000000000001,1018.625 396 | 2014-01-30,14.5,83.25,0.925,1019.375 397 | 2014-01-31,14.75,82.5,3.0125,1019.25 398 | 2014-02-01,14.0,79.375,2.55,1019.75 399 | 2014-02-02,15.25,76.125,1.85,1016.875 400 | 2014-02-03,18.428571428571427,63.857142857142854,12.2875,1014.125 401 | 2014-02-04,18.857142857142858,65.42857142857143,13.424999999999999,1011.0 402 | 2014-02-05,17.0,72.75,1.625,1011.5 403 | 2014-02-06,18.5,65.375,6.012500000000001,1010.875 404 | 2014-02-07,19.625,67.25,15.049999999999999,1009.25 405 | 2014-02-08,13.625,85.375,8.1125,1011.375 406 | 2014-02-09,12.875,63.375,9.2625,1012.0 407 | 2014-02-10,12.625,61.75,3.7,1012.5 408 | 2014-02-11,13.375,64.625,3.7,1013.5 409 | 2014-02-12,13.25,67.0,9.262500000000001,1013.5 410 | 2014-02-13,15.25,64.5,4.875,1013.625 411 | 2014-02-14,13.0,95.5,5.3375,1011.875 412 | 2014-02-15,12.375,89.5,3.9375,1012.875 413 | 2014-02-16,13.5,79.0,8.7875,1016.0 414 | 2014-02-17,15.125,66.125,12.725,1017.75 415 | 2014-02-18,15.25,60.125,8.799999999999999,1017.375 416 | 2014-02-19,15.125,75.375,1.85,1016.625 417 | 2014-02-20,16.125,78.625,1.8625,1014.75 418 | 2014-02-21,17.125,72.25,8.7875,1014.25 419 | 2014-02-22,16.625,85.625,6.250000000000001,1017.375 420 | 2014-02-23,16.5,80.0,7.887500000000001,1019.375 421 | 2014-02-24,16.375,69.375,5.3375,1018.625 422 | 2014-02-25,18.125,69.0,0.925,1017.25 423 | 2014-02-26,18.75,71.125,2.0875,1014.25 424 | 2014-02-27,18.625,76.625,7.175000000000001,1013.125 425 | 2014-02-28,15.625,88.375,5.799999999999999,1013.125 426 | 2014-03-01,16.142857142857142,87.28571428571429,4.242857142857143,1015.2857142857143 427 | 2014-03-02,16.125,79.0,5.7875000000000005,1017.125 428 | 2014-03-03,17.875,65.625,3.7,1016.25 429 | 2014-03-04,18.75,66.375,3.7125000000000004,1016.5 430 | 2014-03-05,18.875,67.0,5.325000000000001,1016.125 431 | 2014-03-06,18.375,64.0,4.6375,1016.375 432 | 2014-03-07,18.5,62.875,6.7125,1017.125 433 | 2014-03-08,19.875,63.875,9.9625,1017.0 434 | 2014-03-09,22.0,56.625,7.65,1014.5 435 | 2014-03-10,20.0,63.875,10.8875,1014.375 436 | 2014-03-11,20.714285714285715,70.0,15.6,1013.7142857142857 437 | 2014-03-12,20.0,68.875,6.4875,1016.25 438 | 2014-03-13,19.0,60.625,9.950000000000001,1016.25 439 | 2014-03-14,19.625,61.25,5.800000000000001,1015.0 440 | 2014-03-15,21.625,62.625,2.7875,1016.375 441 | 2014-03-16,23.125,58.5,6.25,1015.375 442 | 2014-03-17,26.25,52.125,6.7125,1011.25 443 | 2014-03-18,24.857142857142858,53.42857142857143,9.799999999999999,1012.8571428571429 444 | 2014-03-19,21.75,57.375,10.1875,1013.25 445 | 2014-03-20,21.5,55.0,7.4,1010.75 446 | 2014-03-21,22.5,64.75,5.3375,1007.125 447 | 2014-03-22,25.0,59.5,3.0124999999999997,1007.0 448 | 2014-03-23,22.75,61.875,9.025,1010.625 449 | 2014-03-24,22.375,73.125,9.487499999999999,1011.625 450 | 2014-03-25,24.625,60.25,2.775,1010.5 451 | 2014-03-26,25.125,58.875,2.7750000000000004,1010.625 452 | 2014-03-27,23.5,63.25,4.8875,1011.75 453 | 2014-03-28,24.428571428571427,67.57142857142857,4.5,1011.1428571428571 454 | 2014-03-29,25.875,55.625,8.35,1011.25 455 | 2014-03-30,24.125,46.75,13.9,1009.875 456 | 2014-03-31,24.125,44.125,13.200000000000001,1008.25 457 | 2014-04-01,24.875,45.75,9.712499999999999,1006.125 458 | 2014-04-02,26.75,43.375,7.175,1005.75 459 | 2014-04-03,25.0,48.0,10.424999999999999,1010.375 460 | 2014-04-04,25.857142857142858,47.857142857142854,11.914285714285715,1009.8571428571429 461 | 2014-04-05,27.375,44.0,9.2625,1008.875 462 | 2014-04-06,30.125,45.25,6.7125,1007.5 463 | 2014-04-07,27.25,47.625,12.9625,1009.625 464 | 2014-04-08,25.75,41.875,12.725,1010.375 465 | 2014-04-09,25.75,35.25,12.7375,1010.25 466 | 2014-04-10,26.375,29.25,12.500000000000002,1010.875 467 | 2014-04-11,28.142857142857142,29.0,10.057142857142859,1008.5714285714286 468 | 2014-04-12,29.125,39.875,5.1000000000000005,1007.125 469 | 2014-04-13,26.875,40.375,15.5125,1009.625 470 | 2014-04-14,27.625,46.625,5.3500000000000005,1010.625 471 | 2014-04-15,29.0,48.125,2.55,1009.125 472 | 2014-04-16,27.125,53.625,4.4,1008.25 473 | 2014-04-17,26.375,49.625,9.25,1007.0 474 | 2014-04-18,24.25,64.375,14.3375,1011.625 475 | 2014-04-19,25.625,57.875,5.1,1011.375 476 | 2014-04-20,27.0,48.0,4.6375,1010.5 477 | 2014-04-21,28.0,33.125,8.8125,1010.0 478 | 2014-04-22,29.375,29.375,12.5,1011.75 479 | 2014-04-23,29.125,37.0,6.0249999999999995,1009.0 480 | 2014-04-24,29.375,35.875,7.875,1005.875 481 | 2014-04-25,30.625,34.375,8.1125,1006.125 482 | 2014-04-26,31.5,32.0,8.1125,1006.375 483 | 2014-04-27,30.625,30.0,8.350000000000001,1007.125 484 | 2014-04-28,31.5,27.875,10.1875,1005.625 485 | 2014-04-29,32.125,28.875,8.325,1003.875 486 | 2014-04-30,33.25,25.5,5.3375,1003.75 487 | 2014-05-01,34.875,22.5,9.7375,1001.5 488 | 2014-05-02,34.375,24.75,6.4875,1001.875 489 | 2014-05-03,31.75,33.375,6.475,1003.625 490 | 2014-05-04,31.875,34.375,5.8,1003.0 491 | 2014-05-05,29.75,53.25,9.037500000000001,1005.75 492 | 2014-05-06,29.571428571428573,51.142857142857146,6.885714285714287,1006.8571428571429 493 | 2014-05-07,31.75,48.625,1.85,1004.25 494 | 2014-05-08,31.375,43.0,10.662500000000001,1002.625 495 | 2014-05-09,30.5,39.625,6.487500000000001,1000.4285714285714 496 | 2014-05-10,30.0,37.625,7.8875,1000.75 497 | 2014-05-11,29.625,40.375,7.65,1004.0 498 | 2014-05-12,28.0,53.375,10.887500000000001,1004.875 499 | 2014-05-13,25.25,65.75,9.9625,1008.375 500 | 2014-05-14,26.875,55.25,8.575000000000001,1006.5 501 | 2014-05-15,29.5,39.875,11.125,1004.25 502 | 2014-05-16,31.125,40.25,9.4875,1003.5 503 | 2014-05-17,29.875,47.625,7.175000000000001,1007.0 504 | 2014-05-18,31.5,35.375,13.899999999999999,1007.375 505 | 2014-05-19,32.375,29.5,11.575000000000003,1006.625 506 | 2014-05-20,31.125,34.0,7.6375,1006.625 507 | 2014-05-21,33.0,32.5,9.7125,1002.125 508 | 2014-05-22,33.25,33.375,11.112499999999999,999.5 509 | 2014-05-23,34.625,32.75,19.450000000000003,1000.0 510 | 2014-05-24,31.5,50.5,6.725,1004.125 511 | 2014-05-25,31.5,43.25,16.4375,1005.5 512 | 2014-05-26,30.125,54.125,4.175,1007.125 513 | 2014-05-27,31.875,49.75,2.55,1004.75 514 | 2014-05-28,33.625,44.5,3.95,1001.25 515 | 2014-05-29,35.25,40.125,4.4,999.375 516 | 2014-05-30,33.125,44.5,11.5875,999.5714285714286 517 | 2014-05-31,34.625,39.0,6.4875,999.625 518 | 2014-06-01,32.75,34.25,16.2,1003.25 519 | 2014-06-02,32.75,41.375,4.6375,1003.25 520 | 2014-06-03,34.875,33.625,6.487500000000001,1000.0 521 | 2014-06-04,35.25,33.125,7.1875,998.125 522 | 2014-06-05,36.875,26.875,8.5625,996.125 523 | 2014-06-06,37.5,27.625,10.2,994.875 524 | 2014-06-07,38.5,22.75,13.1875,994.0 525 | 2014-06-08,37.625,26.25,6.7250000000000005,993.125 526 | 2014-06-09,37.875,32.125,6.025,993.125 527 | 2014-06-10,37.25,20.875,12.0375,994.75 528 | 2014-06-11,37.625,23.125,14.8125,995.625 529 | 2014-06-12,32.875,39.25,24.325,997.0 530 | 2014-06-13,30.25,55.125,16.6625,997.625 531 | 2014-06-14,30.5,66.875,6.487500000000001,997.375 532 | 2014-06-15,33.875,52.0,8.1,997.125 533 | 2014-06-16,36.375,41.375,10.887500000000001,995.625 534 | 2014-06-17,36.875,42.125,15.962499999999999,994.125 535 | 2014-06-18,33.875,58.0,10.425,994.25 536 | 2014-06-19,35.75,47.875,10.8875,993.25 537 | 2014-06-20,38.0,36.125,17.825,991.375 538 | 2014-06-21,36.875,40.125,9.7375,993.875 539 | 2014-06-22,34.5,46.75,4.4,996.375 540 | 2014-06-23,32.125,49.75,6.7125,996.25 541 | 2014-06-24,33.0,46.25,10.1875,995.5 542 | 2014-06-25,32.25,53.25,13.2,997.625 543 | 2014-06-26,32.5,60.375,3.2375,1001.0 544 | 2014-06-27,34.75,48.5,9.487499999999999,1001.625 545 | 2014-06-28,34.875,49.0,5.7875,999.375 546 | 2014-06-29,33.0,57.75,9.7125,997.75 547 | 2014-06-30,32.0,67.75,3.2499999999999996,997.5 548 | 2014-07-01,31.375,65.125,6.25,1000.875 549 | 2014-07-02,29.5,71.375,5.787500000000001,1001.125 550 | 2014-07-03,28.875,75.125,5.0875,998.2857142857143 551 | 2014-07-04,31.38888888888889,69.0,4.333333333333333,998.7777777777778 552 | 2014-07-05,34.705882352941174,52.05882352941177,9.202941176470588,1000.7058823529412 553 | 2014-07-06,30.625,61.125,3.7125,1000.5 554 | 2014-07-07,34.0,48.75,5.5625,998.25 555 | 2014-07-08,35.0,39.5,9.725000000000001,996.875 556 | 2014-07-09,36.0,37.125,13.65,996.5 557 | 2014-07-10,36.125,38.75,11.3375,996.875 558 | 2014-07-11,36.625,38.625,8.7875,996.125 559 | 2014-07-12,35.875,51.375,5.3375,996.0 560 | 2014-07-13,31.625,69.75,6.025,998.0 561 | 2014-07-14,32.375,67.875,6.725,997.125 562 | 2014-07-15,33.125,62.625,8.825000000000001,996.0 563 | 2014-07-16,32.625,63.0,10.45,997.25 564 | 2014-07-17,30.75,74.25,6.7250000000000005,996.125 565 | 2014-07-18,27.25,83.875,2.55,994.875 566 | 2014-07-19,29.875,71.0,6.25,995.125 567 | 2014-07-20,30.875,68.875,6.262499999999999,996.75 568 | 2014-07-21,32.125,65.5,4.65,996.0 569 | 2014-07-22,31.625,69.75,7.175,997.0 570 | 2014-07-23,30.625,72.875,8.1125,997.75 571 | 2014-07-24,30.5,63.625,12.0375,998.7142857142857 572 | 2014-07-25,30.75,63.25,9.9625,1000.75 573 | 2014-07-26,32.5,61.5,7.175,1001.875 574 | 2014-07-27,31.714285714285715,65.42857142857143,6.25,1001.875 575 | 2014-07-28,30.0,75.75,18.05,999.375 576 | 2014-07-29,30.25,69.875,5.7875000000000005,999.25 577 | 2014-07-30,31.875,65.75,4.175000000000001,999.5714285714286 578 | 2014-07-31,32.5,65.75,7.425,999.625 579 | 2014-08-01,33.25,59.5,6.500000000000001,998.0 580 | 2014-08-02,30.125,77.75,0.925,997.125 581 | 2014-08-03,31.5,66.25,5.7875000000000005,995.375 582 | 2014-08-04,30.5,69.75,5.1000000000000005,996.125 583 | 2014-08-05,29.75,77.5,1.85,997.5 584 | 2014-08-06,32.125,65.0,5.5625,996.875 585 | 2014-08-07,31.125,65.375,6.949999999999999,997.0 586 | 2014-08-08,31.0,67.875,6.012500000000001,998.375 587 | 2014-08-09,29.235294117647058,80.47058823529412,6.652941176470588,1001.6470588235294 588 | 2014-08-10,28.5,89.90625,5.2250000000000005,1002.15625 589 | 2014-08-11,31.125,72.125,3.0124999999999997,999.875 590 | 2014-08-12,32.125,57.5,8.3375,998.75 591 | 2014-08-13,32.125,57.714285714285715,20.825,998.25 592 | 2014-08-14,30.375,57.5,18.5125,997.375 593 | 2014-08-15,31.125,54.875,14.587499999999999,998.875 594 | 2014-08-16,30.428571428571427,59.857142857142854,8.35,1002.5 595 | 2014-08-17,31.375,53.875,7.1875,1003.25 596 | 2014-08-18,31.875,56.625,5.5625,1005.375 597 | 2014-08-19,33.0,52.42857142857143,6.885714285714286,1004.8571428571429 598 | 2014-08-20,33.25,47.125,8.7875,1002.375 599 | 2014-08-21,32.5,43.0,10.8875,1001.25 600 | 2014-08-22,33.0,47.125,8.5625,1002.8571428571429 601 | 2014-08-23,33.125,45.75,9.2625,1003.25 602 | 2014-08-24,33.125,46.25,11.350000000000001,1002.875 603 | 2014-08-25,33.875,46.625,7.874999999999999,1002.375 604 | 2014-08-26,34.125,40.25,9.962499999999999,1001.375 605 | 2014-08-27,33.857142857142854,44.714285714285715,8.112499999999999,1001.125 606 | 2014-08-28,29.625,67.75,6.7250000000000005,1003.75 607 | 2014-08-29,30.375,69.25,3.7,1003.5 608 | 2014-08-30,28.625,78.125,4.1625,1002.625 609 | 2014-08-31,27.625,86.125,4.4125,1001.25 610 | 2014-09-01,29.428571428571427,65.85714285714286,15.5125,1001.125 611 | 2014-09-02,29.25,71.0,10.424999999999999,1001.875 612 | 2014-09-03,28.625,77.875,15.0375,1001.4285714285714 613 | 2014-09-04,27.875,80.75,9.275,1000.375 614 | 2014-09-05,26.5,85.75,8.337499999999999,1000.1428571428571 615 | 2014-09-06,28.5,76.875,2.0875,1002.375 616 | 2014-09-07,31.0,67.625,1.4,1003.25 617 | 2014-09-08,31.25,66.375,3.475,1003.125 618 | 2014-09-09,31.0,70.125,1.85,1002.375 619 | 2014-09-10,28.75,77.5,4.875,1003.25 620 | 2014-09-11,26.571428571428573,85.71428571428571,17.825,1005.5714285714286 621 | 2014-09-12,28.5,73.0,1.625,1005.125 622 | 2014-09-13,29.375,68.25,4.1625,1004.0 623 | 2014-09-14,29.125,65.5,6.712500000000001,1004.25 624 | 2014-09-15,29.625,63.125,6.7125,1004.25 625 | 2014-09-16,30.625,65.125,6.25,1002.25 626 | 2014-09-17,30.625,61.875,7.4,1002.125 627 | 2014-09-18,30.5,62.375,6.7125,1003.875 628 | 2014-09-19,31.0,56.625,4.175000000000001,1003.5 629 | 2014-09-20,31.125,55.75,4.3999999999999995,1002.875 630 | 2014-09-21,30.5,54.875,5.5625,1003.625 631 | 2014-09-22,30.625,50.0,13.437499999999998,1004.5 632 | 2014-09-23,31.0,47.0,30.685714285714283,1004.2857142857143 633 | 2014-09-24,29.625,53.0,8.1,1006.25 634 | 2014-09-25,30.0,52.5,9.725,1007.75 635 | 2014-09-26,30.0,52.5,5.7875,1008.125 636 | 2014-09-27,30.5,46.875,8.799999999999999,1009.25 637 | 2014-09-28,30.25,48.875,7.175,1009.625 638 | 2014-09-29,30.5,48.75,7.4125,1009.125 639 | 2014-09-30,30.625,50.125,6.712500000000001,1009.625 640 | 2014-10-01,30.428571428571427,52.857142857142854,3.1714285714285713,1010.4285714285714 641 | 2014-10-02,30.375,53.375,2.7875,1009.125 642 | 2014-10-03,30.5,52.625,1.85,1008.0 643 | 2014-10-04,31.0,53.5,0.0,1008.5 644 | 2014-10-05,30.625,56.125,0.0,1010.375 645 | 2014-10-06,30.625,56.75,0.0,1010.125 646 | 2014-10-07,31.0,59.375,1.1625,1008.375 647 | 2014-10-08,28.75,52.75,3.7125,1009.875 648 | 2014-10-09,27.625,49.875,3.2375,1010.125 649 | 2014-10-10,27.125,57.375,3.2375000000000003,1008.75 650 | 2014-10-11,25.714285714285715,57.142857142857146,6.9375,1008.25 651 | 2014-10-12,26.0,58.625,0.4625,1008.375 652 | 2014-10-13,27.625,59.75,8.799999999999999,1009.875 653 | 2014-10-14,24.5,67.625,5.325000000000001,1010.875 654 | 2014-10-15,24.5,68.0,3.2625,1013.375 655 | 2014-10-16,23.0,58.875,4.862500000000001,1014.0 656 | 2014-10-17,25.0,47.666666666666664,25.0125,1013.625 657 | 2014-10-18,24.0,56.142857142857146,21.3,1012.875 658 | 2014-10-19,23.75,57.75,4.1625,1012.625 659 | 2014-10-20,24.75,55.625,4.4,1013.125 660 | 2014-10-21,25.625,56.125,2.0875,1014.625 661 | 2014-10-22,25.75,64.875,0.4625,1014.5 662 | 2014-10-23,25.25,66.0,0.925,1012.75 663 | 2014-10-24,25.5,67.5,1.85,1011.75 664 | 2014-10-25,25.25,67.125,0.925,1012.375 665 | 2014-10-26,25.125,65.5,2.0875000000000004,1014.875 666 | 2014-10-27,24.875,62.875,1.8625,1013.75 667 | 2014-10-28,25.125,64.75,2.5625,1012.0 668 | 2014-10-29,24.75,65.75,1.625,1012.125 669 | 2014-10-30,23.875,61.375,1.85,1014.5 670 | 2014-10-31,23.375,58.25,3.7125,1013.625 671 | 2014-11-01,22.5,50.875,5.325000000000001,1014.125 672 | 2014-11-02,22.875,49.125,7.1875,1014.5 673 | 2014-11-03,23.375,52.75,4.1625,1013.5 674 | 2014-11-04,23.625,56.625,3.9375,1012.125 675 | 2014-11-05,22.75,56.25,4.625,1012.375 676 | 2014-11-06,23.0,56.25,6.25,1011.625 677 | 2014-11-07,24.0,66.125,2.3125,1012.875 678 | 2014-11-08,23.875,66.25,1.625,1014.625 679 | 2014-11-09,24.125,56.375,4.1625,1015.125 680 | 2014-11-10,21.375,52.125,3.4750000000000005,1014.0 681 | 2014-11-11,19.25,55.125,2.325,1014.75 682 | 2014-11-12,19.625,52.0,2.3125,1015.875 683 | 2014-11-13,18.5,50.5,3.2375,1016.5 684 | 2014-11-14,18.375,43.0,7.4125000000000005,1016.375 685 | 2014-11-15,18.625,44.75,2.775,1014.875 686 | 2014-11-16,18.25,47.625,2.55,1015.375 687 | 2014-11-17,17.75,51.75,2.7750000000000004,1016.125 688 | 2014-11-18,17.625,53.0,2.0875,1017.875 689 | 2014-11-19,17.5,58.125,1.1625,1016.5 690 | 2014-11-20,20.375,54.625,3.0250000000000004,1013.375 691 | 2014-11-21,18.25,62.125,3.2375,1012.75 692 | 2014-11-22,19.875,58.875,1.625,1011.375 693 | 2014-11-23,17.125,57.375,1.3875,1014.625 694 | 2014-11-24,16.875,52.25,4.175000000000001,1016.0 695 | 2014-11-25,16.5,51.5,2.55,1014.875 696 | 2014-11-26,19.625,51.0,5.550000000000001,1009.875 697 | 2014-11-27,18.125,63.875,0.0,1011.75 698 | 2014-11-28,19.375,67.375,0.0,1012.5 699 | 2014-11-29,20.25,67.625,0.0,1012.375 700 | 2014-11-30,19.75,54.75,2.55,1014.0 701 | 2014-12-01,20.0,42.625,8.799999999999999,1013.75 702 | 2014-12-02,21.375,47.625,7.875000000000001,1012.75 703 | 2014-12-03,25.0,66.57142857142857,11.37142857142857,1007.0 704 | 2014-12-04,21.25,68.875,4.862500000000001,1010.375 705 | 2014-12-05,17.75,59.875,2.55,1017.125 706 | 2014-12-06,18.25,48.875,4.8625,1016.375 707 | 2014-12-07,17.25,48.375,4.862500000000001,1014.75 708 | 2014-12-08,17.0,49.875,2.775,1015.0 709 | 2014-12-09,15.285714285714286,67.57142857142857,0.0,1016.8571428571429 710 | 2014-12-10,21.5,65.5,4.8625,1010.5 711 | 2014-12-11,19.625,70.5,3.25,1011.625 712 | 2014-12-12,17.5,65.625,0.4625,1014.375 713 | 2014-12-13,16.0,71.875,8.8,1016.125 714 | 2014-12-14,16.875,86.625,3.95,1014.5 715 | 2014-12-15,16.625,75.75,2.325,1016.125 716 | 2014-12-16,17.75,72.375,7.425000000000001,1017.375 717 | 2014-12-17,12.875,72.125,6.4875,1020.25 718 | 2014-12-18,11.75,79.75,3.0124999999999997,1021.375 719 | 2014-12-19,11.75,81.75,0.4625,1022.125 720 | 2014-12-20,11.25,77.0,5.800000000000001,1018.75 721 | 2014-12-21,9.5,79.125,0.925,1018.0 722 | 2014-12-22,9.375,85.0,1.3875000000000002,1017.125 723 | 2014-12-23,9.625,78.625,1.625,1017.875 724 | 2014-12-24,9.875,83.0,3.4750000000000005,1021.25 725 | 2014-12-25,9.25,81.125,8.100000000000001,1020.125 726 | 2014-12-26,10.75,77.0,7.6375,1017.75 727 | 2014-12-27,10.375,69.0,2.775,1018.625 728 | 2014-12-28,9.0,86.0,0.7,1019.75 729 | 2014-12-29,11.125,72.625,1.3875,1017.25 730 | 2014-12-30,11.625,70.625,2.55,1014.625 731 | 2014-12-31,12.375,67.125,2.7875,1016.875 732 | 2015-01-01,14.75,72.0,0.925,1017.5 733 | 2015-01-02,14.875,96.625,3.0125,1017.875 734 | 2015-01-03,15.125,92.0,0.925,1017.375 735 | 2015-01-04,14.125,78.75,9.512500000000001,1019.625 736 | 2015-01-05,14.0,69.375,15.049999999999999,1016.0 737 | 2015-01-06,12.0,79.875,4.3999999999999995,1015.5 738 | 2015-01-07,9.625,86.0,3.9375,1016.625 739 | 2015-01-08,10.0,87.875,3.4875,1019.125 740 | 2015-01-09,10.625,80.625,1.625,1020.125 741 | 2015-01-10,11.125,73.125,4.4125000000000005,1018.875 742 | 2015-01-11,11.0,80.75,4.8625,1019.125 743 | 2015-01-12,10.625,79.5,0.925,1019.5 744 | 2015-01-13,12.25,77.875,4.875,1017.125 745 | 2015-01-14,12.0,82.25,5.1000000000000005,1017.5 746 | 2015-01-15,12.375,79.125,0.925,1019.375 747 | 2015-01-16,13.0,78.125,4.6375,1019.25 748 | 2015-01-17,13.5,76.625,1.8625,1019.25 749 | 2015-01-18,13.0,73.875,2.5500000000000003,1018.75 750 | 2015-01-19,13.75,78.0,1.85,1018.875 751 | 2015-01-20,13.375,74.375,7.1875,1020.0 752 | 2015-01-21,13.25,73.375,3.6999999999999997,1021.0 753 | 2015-01-22,12.625,94.125,3.2375,1020.625 754 | 2015-01-23,14.625,85.0,1.1625,1020.0 755 | 2015-01-24,12.375,86.25,9.737499999999999,1020.75 756 | 2015-01-25,13.25,77.75,4.1625,1020.375 757 | 2015-01-26,13.75,88.125,0.925,1019.875 758 | 2015-01-27,13.25,74.25,5.3375,1019.3333333333334 759 | 2015-01-28,11.714285714285714,67.0,27.775000000000002,1018.25 760 | 2015-01-29,11.5,67.5,3.9375,1019.75 761 | 2015-01-30,12.75,56.125,12.037500000000001,1020.375 762 | 2015-01-31,13.75,57.625,9.025,1017.875 763 | 2015-02-01,15.0,65.25,5.1000000000000005,1017.75 764 | 2015-02-02,17.375,63.875,11.8125,1017.5 765 | 2015-02-03,16.75,71.25,7.175000000000002,1020.5 766 | 2015-02-04,14.625,81.125,9.25,1022.0 767 | 2015-02-05,14.625,62.875,7.3999999999999995,1019.25 768 | 2015-02-06,15.125,62.625,3.0125,1019.0 769 | 2015-02-07,15.875,67.625,1.85,1017.0 770 | 2015-02-08,16.625,63.875,4.4125000000000005,1017.375 771 | 2015-02-09,16.875,63.5,5.55,1019.75 772 | 2015-02-10,15.75,64.0,4.625,1019.75 773 | 2015-02-11,16.0,66.625,1.85,1017.125 774 | 2015-02-12,15.5,69.625,6.025,1016.125 775 | 2015-02-13,17.25,59.0,6.725,1012.625 776 | 2015-02-14,19.375,63.125,4.1625,1009.625 777 | 2015-02-15,18.875,70.125,1.3875000000000002,1012.625 778 | 2015-02-16,20.625,71.25,10.425,1013.625 779 | 2015-02-17,21.142857142857142,72.57142857142857,5.028571428571429,1011.8571428571429 780 | 2015-02-18,21.25,72.875,5.1000000000000005,1011.75 781 | 2015-02-19,22.75,73.5,7.65,1012.0 782 | 2015-02-20,22.75,67.125,7.187500000000001,1013.875 783 | 2015-02-21,21.875,63.5,11.3375,1014.125 784 | 2015-02-22,22.142857142857142,58.857142857142854,14.812499999999998,1013.375 785 | 2015-02-23,21.5,74.375,3.0250000000000004,1014.25 786 | 2015-02-24,22.875,72.375,6.0375,1010.625 787 | 2015-02-25,23.125,58.625,6.5,1008.5 788 | 2015-02-26,20.0,34.375,6.7124999999999995,1009.875 789 | 2015-02-27,19.25,43.125,5.800000000000001,1013.0 790 | 2015-02-28,21.25,45.5,6.950000000000001,1013.0 791 | 2015-03-01,17.375,92.75,14.8125,1011.125 792 | 2015-03-02,17.5,89.875,6.725,1012.5 793 | 2015-03-03,16.25,82.5,5.5625,1013.75 794 | 2015-03-04,16.75,69.25,6.7125,1014.0 795 | 2015-03-05,18.75,64.0,3.7,1015.75 796 | 2015-03-06,20.0,62.375,5.0875,1016.375 797 | 2015-03-07,18.375,80.375,4.175,1013.7142857142857 798 | 2015-03-08,19.5,74.125,7.8875,1015.375 799 | 2015-03-09,18.125,54.25,12.2625,1021.0 800 | 2015-03-10,17.75,68.5,8.5625,1019.125 801 | 2015-03-11,18.25,69.875,6.025,1018.125 802 | 2015-03-12,20.5,63.375,5.325000000000001,1017.0 803 | 2015-03-13,21.375,65.25,4.175000000000001,1014.75 804 | 2015-03-14,21.5,64.625,7.875,1015.125 805 | 2015-03-15,17.625,89.125,11.575000000000001,1015.5 806 | 2015-03-16,19.857142857142858,81.85714285714286,8.5625,1017.375 807 | 2015-03-17,20.875,70.375,13.2,1015.875 808 | 2015-03-18,20.25,69.625,12.262500000000001,1013.5 809 | 2015-03-19,21.125,66.0,5.3375,1014.875 810 | 2015-03-20,22.5,65.75,9.025,1014.875 811 | 2015-03-21,24.875,60.5,5.7875,1014.875 812 | 2015-03-22,25.125,64.0,10.2,1012.0 813 | 2015-03-23,24.75,65.75,7.637499999999999,1010.0 814 | 2015-03-24,25.75,65.5,1.3875000000000002,1010.25 815 | 2015-03-25,26.625,65.625,2.55,1011.8571428571429 816 | 2015-03-26,28.5,54.375,9.2625,1013.0 817 | 2015-03-27,27.375,51.375,7.65,1012.0 818 | 2015-03-28,27.0,53.625,3.7125000000000004,1009.625 819 | 2015-03-29,25.0,52.75,13.1875,1008.625 820 | 2015-03-30,23.5,66.25,14.8125,1010.375 821 | 2015-03-31,25.25,64.875,3.7,1010.1428571428571 822 | 2015-04-01,26.428571428571427,59.714285714285715,6.7125,1007.5 823 | 2015-04-02,28.714285714285715,45.285714285714285,10.174999999999999,1004.625 824 | 2015-04-03,24.5,71.125,6.9625,1004.75 825 | 2015-04-04,21.0,77.5,1.3875000000000002,1005.25 826 | 2015-04-05,23.571428571428573,67.71428571428571,17.6,1007.75 827 | 2015-04-06,25.0,60.125,2.775,1007.75 828 | 2015-04-07,23.75,62.125,9.7375,1010.25 829 | 2015-04-08,25.625,50.375,6.712500000000001,1012.0 830 | 2015-04-09,26.0,52.875,9.0375,1010.5 831 | 2015-04-10,27.125,52.25,6.25,1008.5 832 | 2015-04-11,28.25,44.875,8.1,1008.375 833 | 2015-04-12,24.375,63.0,5.325,1012.375 834 | 2015-04-13,24.75,70.875,6.950000000000001,1013.75 835 | 2015-04-14,26.25,62.75,3.0125,1012.75 836 | 2015-04-15,27.625,59.25,5.7875000000000005,1012.875 837 | 2015-04-16,25.25,58.25,8.1125,1013.875 838 | 2015-04-17,28.125,48.75,8.3375,1007.125 839 | 2015-04-18,30.125,44.5,5.562500000000001,1002.375 840 | 2015-04-19,32.125,41.0,4.175,1003.875 841 | 2015-04-20,32.875,37.875,7.1875,1005.75 842 | 2015-04-21,31.75,28.5,11.1125,1005.625 843 | 2015-04-22,30.75,30.25,11.349999999999998,1003.125 844 | 2015-04-23,30.75,33.0,8.1,1004.0 845 | 2015-04-24,31.125,39.5,2.5500000000000003,1004.375 846 | 2015-04-25,30.5,43.125,12.975,1005.875 847 | 2015-04-26,28.75,51.125,15.737499999999999,1008.0 848 | 2015-04-27,30.125,53.0,17.5875,1006.75 849 | 2015-04-28,31.0,48.25,12.5125,1004.5 850 | 2015-04-29,31.25,47.25,9.025,1005.0 851 | 2015-04-30,32.625,44.75,15.05,1004.0 852 | 2015-05-01,31.25,43.5,5.3375,1005.875 853 | 2015-05-02,30.125,40.25,4.4,1006.25 854 | 2015-05-03,31.0,39.25,3.0124999999999997,1006.625 855 | 2015-05-04,30.875,39.25,3.9375,1006.625 856 | 2015-05-05,32.125,31.125,7.6499999999999995,1003.875 857 | 2015-05-06,34.125,22.0,11.362499999999999,1001.875 858 | 2015-05-07,35.42857142857143,13.428571428571429,15.342857142857143,1003.5714285714286 859 | 2015-05-08,34.125,22.375,12.274999999999999,1003.25 860 | 2015-05-09,35.125,27.0,8.3375,1001.625 861 | 2015-05-10,35.125,25.0,6.9625,1001.375 862 | 2015-05-11,34.0,32.625,10.887500000000001,1003.625 863 | 2015-05-12,33.125,46.0,6.0375000000000005,1004.875 864 | 2015-05-13,27.75,61.0,6.7125,1006.125 865 | 2015-05-14,29.625,49.625,4.4125,1007.25 866 | 2015-05-15,28.5,52.125,7.175000000000001,1006.5 867 | 2015-05-16,30.0,51.625,4.1625,1003.25 868 | 2015-05-17,33.25,40.375,8.35,1001.125 869 | 2015-05-18,35.375,35.75,5.562500000000001,1000.0 870 | 2015-05-19,31.5,44.0,14.812500000000002,1002.125 871 | 2015-05-20,33.125,31.625,14.600000000000001,1002.0 872 | 2015-05-21,35.625,24.625,11.3375,1000.375 873 | 2015-05-22,36.25,19.5,12.5125,998.125 874 | 2015-05-23,37.375,21.75,12.725000000000001,996.125 875 | 2015-05-24,36.0,29.25,5.1000000000000005,997.375 876 | 2015-05-25,37.75,22.25,10.875,996.875 877 | 2015-05-26,35.25,26.625,17.125,1001.5 878 | 2015-05-27,33.75,20.5,6.7125,1001.25 879 | 2015-05-28,34.75,29.5,8.3375,1000.625 880 | 2015-05-29,35.125,27.25,8.125,1001.625 881 | 2015-05-30,32.25,38.75,12.2625,1003.875 882 | 2015-05-31,34.25,25.0,14.5875,1001.75 883 | 2015-06-01,29.375,41.75,12.512500000000001,1003.25 884 | 2015-06-02,31.625,39.25,8.575,1003.5 885 | 2015-06-03,28.625,50.5,6.7250000000000005,1004.625 886 | 2015-06-04,31.875,48.375,4.8625,1004.0 887 | 2015-06-05,32.625,39.875,6.0125,1003.5 888 | 2015-06-06,33.375,39.75,7.862500000000001,1001.375 889 | 2015-06-07,35.0,36.25,7.175000000000001,999.25 890 | 2015-06-08,36.5,33.0,6.25,998.875 891 | 2015-06-09,37.625,23.875,18.75,998.875 892 | 2015-06-10,36.625,32.285714285714285,18.049999999999997,999.625 893 | 2015-06-11,35.625,34.75,18.287499999999998,1000.0 894 | 2015-06-12,35.875,34.625,12.737499999999999,998.5 895 | 2015-06-13,31.125,48.125,10.1875,998.8571428571429 896 | 2015-06-14,27.0,77.625,3.95,1000.2857142857143 897 | 2015-06-15,31.0,60.75,3.0125,999.5714285714286 898 | 2015-06-16,32.375,52.125,2.7875,999.1428571428571 899 | 2015-06-17,33.875,49.75,3.25,998.0 900 | 2015-06-18,35.125,45.125,3.0125,997.625 901 | 2015-06-19,35.875,46.75,8.575,996.25 902 | 2015-06-20,33.125,48.5,12.975,997.875 903 | 2015-06-21,30.375,63.625,8.1125,998.125 904 | 2015-06-22,32.125,59.5,4.4,995.625 905 | 2015-06-23,31.5,61.625,6.012500000000001,995.0 906 | 2015-06-24,37.25,63.875,6.5,995.375 907 | 2015-06-25,26.625,82.75,6.2625,994.75 908 | 2015-06-26,31.125,54.25,11.5625,995.25 909 | 2015-06-27,32.625,48.25,14.112499999999999,997.125 910 | 2015-06-28,34.25,47.5,9.0375,1000.2857142857143 911 | 2015-06-29,31.125,56.625,11.562500000000002,1003.625 912 | 2015-06-30,30.875,58.625,9.9625,1002.625 913 | 2015-07-01,32.0,53.25,4.175000000000001,1001.875 914 | 2015-07-02,33.75,45.875,6.949999999999999,1001.125 915 | 2015-07-03,33.0,49.0,14.35,999.625 916 | 2015-07-04,34.125,51.5,16.900000000000002,997.5 917 | 2015-07-05,35.714285714285715,47.285714285714285,14.014285714285714,995.4285714285714 918 | 2015-07-06,27.875,83.25,9.275,997.875 919 | 2015-07-07,28.125,86.125,1.85,998.125 920 | 2015-07-08,31.25,72.0,2.7750000000000004,998.625 921 | 2015-07-09,29.625,77.25,2.55,998.625 922 | 2015-07-10,26.625,92.125,6.025,998.75 923 | 2015-07-11,25.5,98.0,3.2375000000000003,999.3333333333334 924 | 2015-07-12,25.5,94.75,7.425000000000001,998.625 925 | 2015-07-13,29.875,73.125,4.8625,1000.625 926 | 2015-07-14,30.875,67.875,10.875,997.8571428571429 927 | 2015-07-15,33.375,52.875,17.137500000000003,993.75 928 | 2015-07-16,31.75,61.375,13.437499999999998,994.75 929 | 2015-07-17,30.75,75.625,4.4,997.25 930 | 2015-07-18,29.5,78.875,4.175,998.0 931 | 2015-07-19,30.125,70.0,0.7,997.625 932 | 2015-07-20,29.125,78.0,4.8625,999.125 933 | 2015-07-21,31.375,69.25,8.125,1000.0 934 | 2015-07-22,32.25,64.125,7.875000000000002,1001.125 935 | 2015-07-23,32.125,64.875,8.575000000000001,1001.75 936 | 2015-07-24,32.0,64.75,6.7125,1001.0 937 | 2015-07-25,30.857142857142858,71.0,19.9125,998.5714285714286 938 | 2015-07-26,30.0,71.625,16.8875,998.875 939 | 2015-07-27,29.625,69.0,11.3375,1000.2857142857143 940 | 2015-07-28,29.5,61.0,11.587499999999999,1000.125 941 | 2015-07-29,28.5,68.125,10.875,998.0 942 | 2015-07-30,29.0,67.75,9.9625,998.875 943 | 2015-07-31,29.0,71.0,4.175000000000001,999.75 944 | 2015-08-01,28.25,71.75,0.925,1002.75 945 | 2015-08-02,29.625,69.5,2.55,1003.625 946 | 2015-08-03,31.5,58.625,5.325,1001.875 947 | 2015-08-04,29.428571428571427,77.78571428571429,4.771428571428572,1000.0714285714286 948 | 2015-08-05,29.59375,79.65625,8.221875,1000.28125 949 | 2015-08-06,29.5,79.125,3.0250000000000004,998.0 950 | 2015-08-07,29.25,83.0,2.3125,1000.0 951 | 2015-08-08,28.875,84.875,1.625,1002.0 952 | 2015-08-09,27.375,90.125,3.0125,1000.25 953 | 2015-08-10,30.375,77.0,0.0,1000.0 954 | 2015-08-11,28.375,86.625,0.4625,1003.0 955 | 2015-08-12,30.75,74.25,0.0,1003.125 956 | 2015-08-13,32.0,69.5,0.4625,1001.875 957 | 2015-08-14,30.875,72.625,3.4749999999999996,1002.0 958 | 2015-08-15,28.5,85.5,0.0,1002.75 959 | 2015-08-16,28.625,82.625,1.85,1002.875 960 | 2015-08-17,30.75,72.625,0.7,1001.375 961 | 2015-08-18,29.125,72.875,4.875,1002.5714285714286 962 | 2015-08-19,31.25,61.25,33.325,1002.375 963 | 2015-08-20,30.75,58.625,14.6,1001.375 964 | 2015-08-21,29.375,72.0,8.575,1002.0 965 | 2015-08-22,30.25,65.375,5.800000000000001,1000.875 966 | 2015-08-23,29.875,69.375,5.5625,1000.8571428571429 967 | 2015-08-24,30.75,65.625,7.1875,1001.75 968 | 2015-08-25,32.0,57.285714285714285,10.071428571428571,1001.0 969 | 2015-08-26,32.0,61.0,4.862500000000001,1001.125 970 | 2015-08-27,32.5,61.0,5.325000000000001,1002.0 971 | 2015-08-28,32.0,63.5,2.3125,1002.25 972 | 2015-08-29,32.25,58.0,5.812500000000001,1001.375 973 | 2015-08-30,32.625,56.5,9.274999999999999,1001.375 974 | 2015-08-31,31.857142857142858,54.42857142857143,13.485714285714286,1001.8333333333334 975 | 2015-09-01,32.125,47.625,15.275,1000.8571428571429 976 | 2015-09-02,31.875,45.625,12.737499999999999,1001.75 977 | 2015-09-03,31.625,49.125,10.1875,1005.5 978 | 2015-09-04,32.0,42.625,11.35,1007.875 979 | 2015-09-05,30.875,47.25,7.637499999999999,1007.125 980 | 2015-09-06,31.25,41.8,7.6375,1006.25 981 | 2015-09-07,31.5,46.375,9.4875,1005.75 982 | 2015-09-08,31.625,47.0,11.8125,1005.5 983 | 2015-09-09,31.0,47.875,10.1875,1005.625 984 | 2015-09-10,31.25,46.5,8.575000000000001,1006.75 985 | 2015-09-11,30.75,49.75,6.012499999999999,1006.25 986 | 2015-09-12,32.142857142857146,54.42857142857143,3.1714285714285717,1006.0 987 | 2015-09-13,32.125,55.25,1.3875000000000002,1005.875 988 | 2015-09-14,31.875,54.375,2.55,1003.625 989 | 2015-09-15,33.0,55.0,0.4625,1003.25 990 | 2015-09-16,32.375,55.0,1.4,1003.625 991 | 2015-09-17,31.375,58.375,1.1625,1004.375 992 | 2015-09-18,31.0,67.625,7.4125000000000005,1005.125 993 | 2015-09-19,30.625,74.25,2.0875000000000004,1003.625 994 | 2015-09-20,29.142857142857142,69.14285714285714,5.557142857142858,1002.8571428571429 995 | 2015-09-21,30.125,64.5,5.800000000000002,1002.5 996 | 2015-09-22,30.25,64.375,10.187499999999998,1002.5 997 | 2015-09-23,28.625,61.25,13.675,1006.375 998 | 2015-09-24,28.25,52.375,11.575,1004.625 999 | 2015-09-25,28.5,49.375,8.575,1003.75 1000 | 2015-09-26,29.25,53.5,6.25,1005.25 1001 | 2015-09-27,28.714285714285715,58.142857142857146,2.9142857142857146,1007.0 1002 | 2015-09-28,28.25,58.5,3.9375,1009.0 1003 | 2015-09-29,29.0,54.57142857142857,9.528571428571428,1010.5714285714286 1004 | 2015-09-30,29.0,46.875,8.3375,1011.375 1005 | 2015-10-01,28.125,51.25,5.574999999999999,1012.0 1006 | 2015-10-02,28.285714285714285,53.714285714285715,14.5875,1010.75 1007 | 2015-10-03,30.0,52.333333333333336,18.785714285714285,1008.7142857142857 1008 | 2015-10-04,29.0,54.375,3.0250000000000004,1008.125 1009 | 2015-10-05,28.875,59.375,1.3875000000000002,1009.25 1010 | 2015-10-06,28.375,61.125,4.6375,1009.0 1011 | 2015-10-07,28.625,65.75,6.4750000000000005,1009.125 1012 | 2015-10-08,28.375,65.125,4.4125000000000005,1007.5 1013 | 2015-10-09,28.5,59.75,6.025,1004.25 1014 | 2015-10-10,28.5,54.625,6.7125,1005.0 1015 | 2015-10-11,28.571428571428573,51.57142857142857,6.885714285714286,1008.7142857142857 1016 | 2015-10-12,29.25,54.125,3.25,1010.0 1017 | 2015-10-13,29.5,62.875,3.7250000000000005,1011.0 1018 | 2015-10-14,28.125,63.625,4.875000000000001,1013.25 1019 | 2015-10-15,27.5,66.0,0.4625,1013.5 1020 | 2015-10-16,27.875,62.625,0.925,1013.5 1021 | 2015-10-17,28.0,63.75,3.7125,1012.25 1022 | 2015-10-18,26.857142857142858,71.71428571428571,11.1125,1013.125 1023 | 2015-10-19,28.142857142857142,66.28571428571429,3.4571428571428577,1011.7142857142857 1024 | 2015-10-20,27.714285714285715,64.85714285714286,2.9142857142857146,1009.8571428571429 1025 | 2015-10-21,26.5,62.125,2.0875,1011.875 1026 | 2015-10-22,24.75,56.0,4.4,1012.25 1027 | 2015-10-23,24.0,51.625,4.862500000000001,1011.875 1028 | 2015-10-24,24.875,53.25,1.4000000000000001,1011.625 1029 | 2015-10-25,26.375,47.125,5.575,1012.75 1030 | 2015-10-26,25.625,52.125,5.574999999999999,1014.875 1031 | 2015-10-27,24.75,54.25,3.4749999999999996,1013.875 1032 | 2015-10-28,20.875,57.25,7.875000000000001,1015.875 1033 | 2015-10-29,22.125,63.5,1.85,1016.875 1034 | 2015-10-30,22.375,67.25,1.625,1016.375 1035 | 2015-10-31,22.375,69.375,1.1625,1016.125 1036 | 2015-11-01,22.875,70.5,0.925,1016.25 1037 | 2015-11-02,23.375,71.375,0.4625,1015.875 1038 | 2015-11-03,24.0,67.875,0.475,1014.25 1039 | 2015-11-04,23.75,69.0,0.925,1012.25 1040 | 2015-11-05,22.125,75.25,5.100000000000001,1012.375 1041 | 2015-11-06,22.875,74.375,2.325,1012.75 1042 | 2015-11-07,21.875,74.875,1.1625,1014.625 1043 | 2015-11-08,21.875,70.25,1.8624999999999998,1015.875 1044 | 2015-11-09,21.75,73.625,0.7,1015.0 1045 | 2015-11-10,22.75,72.625,0.7,1013.5 1046 | 2015-11-11,21.875,59.5,4.6375,1014.375 1047 | 2015-11-12,21.875,60.5,5.7875,1015.375 1048 | 2015-11-13,22.375,59.375,5.325,1014.875 1049 | 2015-11-14,22.375,59.625,3.475,1012.0 1050 | 2015-11-15,22.5,62.0,0.0,1012.0 1051 | 2015-11-16,21.875,50.375,2.7874999999999996,1012.625 1052 | 2015-11-17,19.875,48.375,5.1000000000000005,1013.5 1053 | 2015-11-18,18.5,57.625,4.4,1016.375 1054 | 2015-11-19,18.5,64.75,0.925,1017.5 1055 | 2015-11-20,18.5,66.0,0.4625,1016.125 1056 | 2015-11-21,18.75,67.625,1.3875000000000002,1015.5 1057 | 2015-11-22,18.625,64.875,0.4625,1015.125 1058 | 2015-11-23,17.0,73.5,0.0,1015.875 1059 | 2015-11-24,18.5,63.0,1.625,1016.0 1060 | 2015-11-25,19.375,56.375,5.7875000000000005,1014.75 1061 | 2015-11-26,20.0,59.5,0.925,1012.75 1062 | 2015-11-27,18.5,66.25,2.55,1011.25 1063 | 2015-11-28,18.75,82.125,1.1625,1015.375 1064 | 2015-11-29,18.125,81.25,1.1625,1018.0 1065 | 2015-11-30,19.5,82.375,0.925,1016.75 1066 | 2015-12-01,19.25,78.75,1.625,1014.75 1067 | 2015-12-02,18.125,72.125,2.0875,1016.125 1068 | 2015-12-03,17.625,70.25,7.1875,1016.0 1069 | 2015-12-04,17.625,67.5,4.6375,1015.0 1070 | 2015-12-05,17.5,72.375,0.925,1015.875 1071 | 2015-12-06,17.125,75.875,1.4000000000000001,1016.5 1072 | 2015-12-07,17.625,76.875,2.3125,1017.25 1073 | 2015-12-08,18.428571428571427,71.28571428571429,1.0571428571428572,1015.7142857142857 1074 | 2015-12-09,18.375,78.125,3.475,1015.25 1075 | 2015-12-10,19.0,77.25,2.0875000000000004,1011.75 1076 | 2015-12-11,17.25,77.875,5.1000000000000005,1011.375 1077 | 2015-12-12,13.25,91.0,4.3999999999999995,1014.25 1078 | 2015-12-13,12.875,67.0,3.475,1016.5 1079 | 2015-12-14,13.25,63.625,7.400000000000001,1017.0 1080 | 2015-12-15,13.125,63.875,5.325,1016.5 1081 | 2015-12-16,13.0,65.5,1.1625,1016.375 1082 | 2015-12-17,12.25,73.625,0.925,1016.875 1083 | 2015-12-18,12.875,71.25,4.4,1018.625 1084 | 2015-12-19,12.375,71.875,3.4875,1019.0 1085 | 2015-12-20,11.75,70.25,1.1625,1018.5 1086 | 2015-12-21,12.625,74.75,1.3875000000000002,1018.875 1087 | 2015-12-22,12.0,79.875,0.7,1020.875 1088 | 2015-12-23,12.5,81.375,3.4875000000000003,1020.625 1089 | 2015-12-24,11.25,68.625,6.025,1021.25 1090 | 2015-12-25,11.5,66.125,5.5625,1022.0 1091 | 2015-12-26,12.75,64.5,6.4875,1021.125 1092 | 2015-12-27,15.375,63.25,7.8875,1020.625 1093 | 2015-12-28,17.125,58.125,10.8875,1020.875 1094 | 2015-12-29,16.375,65.0,7.4125000000000005,1018.125 1095 | 2015-12-30,15.5,71.75,2.0999999999999996,1017.5 1096 | 2015-12-31,15.0,71.375,2.0875,1020.5 1097 | 2016-01-01,14.714285714285714,72.28571428571429,1.0571428571428572,1021.1428571428571 1098 | 2016-01-02,14.0,75.875,2.0875,1021.0 1099 | 2016-01-03,14.375,74.75,5.112500000000001,1018.5 1100 | 2016-01-04,15.75,77.125,0.0,1017.625 1101 | 2016-01-05,15.833333333333334,88.83333333333333,0.6166666666666667,1017.0 1102 | 2016-01-06,17.375,81.625,2.3125,1016.5 1103 | 2016-01-07,17.125,87.0,0.0,1018.125 1104 | 2016-01-08,15.5,83.25,7.887500000000001,1017.25 1105 | 2016-01-09,15.857142857142858,65.14285714285714,8.471428571428572,1015.4285714285714 1106 | 2016-01-10,15.625,74.375,2.775,1017.5 1107 | 2016-01-11,15.75,74.875,1.625,1017.5 1108 | 2016-01-12,18.0,69.6875,6.03125,1014.9375 1109 | 2016-01-13,18.266666666666666,75.8,5.08,1014.3333333333334 1110 | 2016-01-14,15.5625,80.4375,4.300000000000001,1016.125 1111 | 2016-01-15,13.0,84.1875,10.775,1017.0 1112 | 2016-01-16,13.6,80.06666666666666,11.239999999999998,1014.6 1113 | 2016-01-17,14.0,77.66666666666667,2.1066666666666665,1016.2 1114 | 2016-01-18,13.266666666666667,80.46666666666667,1.4800000000000002,1014.7333333333333 1115 | 2016-01-19,12.357142857142858,88.57142857142857,4.65,1015.2857142857143 1116 | 2016-01-20,12.066666666666666,84.2,6.193333333333333,1018.6666666666666 1117 | 2016-01-21,12.1875,73.1875,7.887499999999998,1020.1875 1118 | 2016-01-22,11.733333333333333,83.4,3.586666666666667,1019.4666666666667 1119 | 2016-01-23,14.4375,75.875,3.2500000000000004,1019.8125 1120 | 2016-01-24,11.1875,86.25,6.624999999999999,1019.0625 1121 | 2016-01-25,11.666666666666666,75.4,4.58,1017.2 1122 | 2016-01-26,14.5625,79.6875,2.5500000000000003,1015.375 1123 | 2016-01-27,17.583333333333332,69.16666666666667,4.025,1014.1666666666666 1124 | 2016-01-28,16.857142857142858,69.35714285714286,5.028571428571429,1015.0714285714286 1125 | 2016-01-29,19.5625,70.625,2.54375,1017.375 1126 | 2016-01-30,20.142857142857142,78.5,1.2,1014.5 1127 | 2016-01-31,17.375,78.25,7.762500000000001,1016.0 1128 | 2016-02-01,15.846153846153847,65.76923076923077,7.269230769230768,1017.1538461538462 1129 | 2016-02-02,15.266666666666667,55.86666666666667,8.893333333333333,1018.2 1130 | 2016-02-03,13.125,66.5,7.622222222222222,1018.0 1131 | 2016-02-04,16.363636363636363,53.0,6.941666666666666,1017.3333333333334 1132 | 2016-02-05,12.666666666666666,70.66666666666667,2.1666666666666665,1016.8333333333334 1133 | 2016-02-06,15.333333333333334,69.5,3.6999999999999997,1013.3333333333334 1134 | 2016-02-07,15.625,77.0,2.7875,1015.75 1135 | 2016-02-08,17.09090909090909,67.18181818181819,5.9,1017.0909090909091 1136 | 2016-02-09,17.76923076923077,67.46153846153847,5.846153846153848,1014.6923076923077 1137 | 2016-02-10,18.133333333333333,64.86666666666666,2.1,1012.7333333333333 1138 | 2016-02-11,19.6875,54.75,6.03125,1011.25 1139 | 2016-02-12,19.2,57.86666666666667,0.7466666666666666,1012.5333333333333 1140 | 2016-02-13,17.066666666666666,62.13333333333333,5.8066666666666675,1014.8666666666667 1141 | 2016-02-14,17.642857142857142,61.92857142857143,8.071428571428571,1017.2142857142857 1142 | 2016-02-15,17.6,59.53333333333333,5.94,1018.7333333333333 1143 | 2016-02-16,18.214285714285715,56.142857142857146,5.435714285714285,1020.0714285714286 1144 | 2016-02-17,18.714285714285715,59.285714285714285,3.7071428571428577,1019.1428571428571 1145 | 2016-02-18,19.466666666666665,76.0,5.186666666666667,1017.4 1146 | 2016-02-19,23.153846153846153,70.23076923076923,4.1461538461538465,1014.6153846153846 1147 | 2016-02-20,23.625,73.125,11.11875,1014.125 1148 | 2016-02-21,21.0,61.125,17.137500000000003,1014.75 1149 | 2016-02-22,21.0,51.42857142857143,15.207142857142859,1014.4285714285714 1150 | 2016-02-23,21.428571428571427,50.642857142857146,9.785714285714286,1012.5 1151 | 2016-02-24,21.6875,58.0625,2.4437500000000005,1012.9375 1152 | 2016-02-25,22.5625,57.5,5.3375,1015.5 1153 | 2016-02-26,22.8,55.6,2.48,1018.4 1154 | 2016-02-27,22.8,59.0,3.333333333333334,1018.4666666666667 1155 | 2016-02-28,23.0,63.0625,3.712500000000001,1018.9375 1156 | 2016-02-29,23.875,63.9375,3.712500000000001,1018.625 1157 | 2016-03-01,24.916666666666668,57.416666666666664,1.7,1015.0 1158 | 2016-03-02,24.933333333333334,57.06666666666667,1.7333333333333334,1015.4 1159 | 2016-03-03,26.0,55.61538461538461,4.292307692307692,1014.6153846153846 1160 | 2016-03-04,27.3125,44.25,6.14375,1012.6875 1161 | 2016-03-05,23.933333333333334,57.13333333333333,11.0,1012.6666666666666 1162 | 2016-03-06,22.8125,66.5625,5.21875,1013.0625 1163 | 2016-03-07,23.714285714285715,64.14285714285714,5.171428571428572,1012.3571428571429 1164 | 2016-03-08,23.428571428571427,60.214285714285715,6.742857142857142,1011.6428571428571 1165 | 2016-03-09,24.0,57.5625,9.375,1012.0625 1166 | 2016-03-10,25.5625,57.4375,7.981249999999999,1013.1875 1167 | 2016-03-11,25.066666666666666,67.8,6.546666666666667,1012.5333333333333 1168 | 2016-03-12,24.5625,66.9375,9.487499999999999,1009.5625 1169 | 2016-03-13,24.25,65.1875,6.84375,1011.1875 1170 | 2016-03-14,22.375,66.0,6.275,1014.3125 1171 | 2016-03-15,24.066666666666666,58.93333333333333,8.646666666666667,1014.8666666666667 1172 | 2016-03-16,23.9375,53.75,10.88125,1012.8125 1173 | 2016-03-17,26.3125,50.3125,6.84375,1010.4375 1174 | 2016-03-18,26.1875,61.25,6.7125,1009.8125 1175 | 2016-03-19,26.785714285714285,61.857142857142854,3.578571428571429,1009.2142857142857 1176 | 2016-03-20,27.133333333333333,58.733333333333334,2.8466666666666667,1010.3333333333334 1177 | 2016-03-21,26.625,43.375,12.2625,1011.125 1178 | 2016-03-22,25.0625,37.875,22.1,1009.375 1179 | 2016-03-23,26.2,43.266666666666666,10.641666666666667,1010.0 1180 | 2016-03-24,28.133333333333333,46.86666666666667,3.2133333333333334,1013.3333333333334 1181 | 2016-03-25,29.875,50.1875,3.3687500000000004,1013.1875 1182 | 2016-03-26,24.666666666666668,62.666666666666664,9.393333333333334,1013.6 1183 | 2016-03-27,26.25,53.6875,9.156249999999996,1012.8125 1184 | 2016-03-28,25.933333333333334,52.8,6.546666666666667,7679.333333333333 1185 | 2016-03-29,27.125,47.75,7.293749999999999,1011.3125 1186 | 2016-03-30,29.571428571428573,39.785714285714285,7.021428571428571,1011.1428571428571 1187 | 2016-03-31,30.0,39.733333333333334,4.333333333333333,1009.2 1188 | 2016-04-01,30.571428571428573,38.214285714285715,12.035714285714283,1006.7142857142857 1189 | 2016-04-02,32.3125,38.5625,5.4625,1008.0 1190 | 2016-04-03,33.3125,44.25,2.55,1008.375 1191 | 2016-04-04,32.8125,33.125,8.0,1006.6875 1192 | 2016-04-05,32.3125,29.3125,9.268749999999999,1007.0 1193 | 2016-04-06,31.375,33.125,3.475,1006.875 1194 | 2016-04-07,29.933333333333334,37.0,10.85,1007.2666666666667 1195 | 2016-04-08,29.266666666666666,36.0,4.686666666666667,1009.2 1196 | 2016-04-09,30.733333333333334,32.46666666666667,12.61875,1009.3125 1197 | 2016-04-10,32.25,29.0,5.225,1008.8125 1198 | 2016-04-11,29.8,35.0,16.04,1009.4 1199 | 2016-04-12,30.2,36.2,19.380000000000003,1006.3333333333334 1200 | 2016-04-13,31.75,28.0625,18.643749999999997,1003.3125 1201 | 2016-04-14,33.125,23.875,10.425,1003.8125 1202 | 2016-04-15,33.625,25.375,9.8375,1004.625 1203 | 2016-04-16,35.6875,23.6875,11.112499999999999,1003.0625 1204 | 2016-04-17,34.666666666666664,23.866666666666667,9.386666666666665,1002.6666666666666 1205 | 2016-04-18,34.625,30.125,7.175,1004.6875 1206 | 2016-04-19,34.0,33.6,4.566666666666666,1005.8666666666667 1207 | 2016-04-20,34.0625,25.9375,14.01875,1006.4375 1208 | 2016-04-21,34.0,24.666666666666668,15.75,1003.4 1209 | 2016-04-22,33.25,24.25,8.35,1001.7272727272727 1210 | 2016-04-23,31.916666666666668,21.818181818181817,19.653846153846157,1001.8461538461538 1211 | 2016-04-24,31.3125,22.125,10.1875,1003.0625 1212 | 2016-04-25,31.75,22.8125,6.949999999999999,1005.5625 1213 | 2016-04-26,33.4375,25.1875,3.8312500000000003,1007.1875 1214 | 2016-04-27,33.125,29.0625,4.981249999999999,1005.6875 1215 | 2016-04-28,34.15384615384615,21.76923076923077,11.823076923076922,1004.6923076923077 1216 | 2016-04-29,34.07142857142857,23.285714285714285,9.123076923076923,1005.7142857142857 1217 | 2016-04-30,33.0625,25.5625,5.2125,1004.8125 1218 | 2016-05-01,34.6875,23.375,8.575,1002.4375 1219 | 2016-05-02,38.0,18.466666666666665,9.513333333333334,1002.2 1220 | 2016-05-03,35.5,30.375,6.4937499999999995,1004.3125 1221 | 2016-05-04,33.714285714285715,31.857142857142858,10.464285714285717,1003.7857142857143 1222 | 2016-05-05,30.6,42.0,11.246666666666666,1007.4666666666667 1223 | 2016-05-06,31.4375,37.4375,8.225,1007.0 1224 | 2016-05-07,33.3125,37.1875,4.65,1005.5625 1225 | 2016-05-08,35.13333333333333,36.333333333333336,7.293333333333332,1005.9333333333333 1226 | 2016-05-09,33.53333333333333,40.6,18.425,1006.0625 1227 | 2016-05-10,34.0,39.75,10.55625,1006.5625 1228 | 2016-05-11,32.5,49.9375,7.774999999999999,1006.0625 1229 | 2016-05-12,35.375,40.0,5.1,1002.25 1230 | 2016-05-13,37.294117647058826,28.11764705882353,8.07058823529412,1001.3529411764706 1231 | 2016-05-14,36.5625,31.875,4.16875,1003.125 1232 | 2016-05-15,37.25,29.5,4.75,1003.5625 1233 | 2016-05-16,37.214285714285715,23.5,9.019999999999998,1000.8 1234 | 2016-05-17,37.5,25.8125,9.612499999999999,998.9375 1235 | 2016-05-18,37.75,27.625,8.45,997.8125 1236 | 2016-05-19,37.375,40.5,9.3875,998.625 1237 | 2016-05-20,37.4,44.2,8.413333333333334,996.9333333333333 1238 | 2016-05-21,36.13333333333333,45.4,10.873333333333333,995.9333333333333 1239 | 2016-05-22,36.8,44.8,6.553333333333333,996.4666666666667 1240 | 2016-05-23,32.214285714285715,47.92857142857143,14.95,999.5714285714286 1241 | 2016-05-24,31.526315789473685,53.526315789473685,8.494736842105263,1000.7368421052631 1242 | 2016-05-25,33.21739130434783,46.608695652173914,10.160869565217393,997.4782608695652 1243 | 2016-05-26,35.26923076923077,44.57692307692308,15.17692307692308,999.1538461538462 1244 | 2016-05-27,38.27272727272727,26.09090909090909,15.31818181818182,999.9545454545455 1245 | 2016-05-28,36.0625,37.25,14.405555555555553,999.8333333333334 1246 | 2016-05-29,31.5,57.0625,11.68125,1001.25 1247 | 2016-05-30,26.8125,74.8125,9.4875,1003.8125 1248 | 2016-05-31,32.642857142857146,51.214285714285715,7.4071428571428575,1002.3076923076923 1249 | 2016-06-01,36.0,42.2,9.133333333333335,1002.7333333333333 1250 | 2016-06-02,37.5625,35.3125,11.93125,1002.0 1251 | 2016-06-03,37.5625,40.6875,2.7812500000000004,1002.25 1252 | 2016-06-04,38.2,42.06666666666667,3.2066666666666666,1001.8666666666667 1253 | 2016-06-05,36.166666666666664,51.75,8.5,1002.8333333333334 1254 | 2016-06-06,35.42857142857143,45.714285714285715,11.0,1001.5714285714286 1255 | 2016-06-07,34.625,59.1875,21.306250000000002,1002.0625 1256 | 2016-06-08,36.07142857142857,44.642857142857146,8.485714285714284,999.2142857142857 1257 | 2016-06-09,35.733333333333334,43.733333333333334,9.26,938.0666666666667 1258 | 2016-06-10,36.13333333333333,41.86666666666667,10.08125,998.25 1259 | 2016-06-11,33.4375,49.9375,8.812500000000002,999.0 1260 | 2016-06-12,35.5,37.125,9.1625,998.8125 1261 | 2016-06-13,36.0,43.3125,12.85,998.1875 1262 | 2016-06-14,32.625,55.125,11.859999999999998,997.8125 1263 | 2016-06-15,34.733333333333334,48.86666666666667,10.513333333333332,996.1333333333333 1264 | 2016-06-16,33.5,51.285714285714285,11.39285714285714,999.0 1265 | 2016-06-17,34.1875,52.25,7.425,999.4285714285714 1266 | 2016-06-18,35.857142857142854,50.57142857142857,10.857142857142858,1000.8571428571429 1267 | 2016-06-19,35.625,53.25,9.387499999999998,1001.375 1268 | 2016-06-20,30.9375,73.9375,7.643750000000001,1000.6875 1269 | 2016-06-21,32.875,62.1875,8.91875,999.9375 1270 | 2016-06-22,33.125,60.8125,7.418750000000001,1001.5625 1271 | 2016-06-23,33.84615384615385,51.61538461538461,5.875000000000001,999.6923076923077 1272 | 2016-06-24,36.4375,46.5625,7.762499999999999,998.3125 1273 | 2016-06-25,35.42857142857143,55.92857142857143,9.664285714285713,1000.0714285714286 1274 | 2016-06-26,34.86666666666667,57.46666666666667,7.660000000000001,1000.375 1275 | 2016-06-27,34.3125,61.25,7.9937499999999995,998.8666666666667 1276 | 2016-06-28,30.785714285714285,75.07142857142857,5.435714285714285,1001.5 1277 | 2016-06-29,35.375,58.875,8.449999999999998,1001.1875 1278 | 2016-06-30,35.46666666666667,60.2,6.793333333333333,997.8666666666667 1279 | 2016-07-01,32.125,73.4,8.35,1002.5625 1280 | 2016-07-02,28.4,80.0,5.231250000000001,998.375 1281 | 2016-07-03,29.5625,78.125,1.96875,998.25 1282 | 2016-07-04,30.6875,76.3125,3.481250000000001,997.125 1283 | 2016-07-05,33.25,68.6875,5.3375,995.5625 1284 | 2016-07-06,33.266666666666666,64.06666666666666,8.878571428571428,996.8666666666667 1285 | 2016-07-07,33.5,63.0,5.91875,997.6875 1286 | 2016-07-08,30.8,75.86666666666666,5.4333333333333345,997.4 1287 | 2016-07-09,33.25,66.0625,6.73125,996.75 1288 | 2016-07-10,32.5625,65.6875,4.7,996.0625 1289 | 2016-07-11,31.5,71.9375,8.046666666666665,997.75 1290 | 2016-07-12,30.5,80.1875,5.5625,999.0625 1291 | 2016-07-13,31.25,76.3125,6.806666666666667,998.4375 1292 | 2016-07-14,30.4375,80.4375,5.21875,998.375 1293 | 2016-07-15,31.0,78.0625,7.271428571428571,998.625 1294 | 2016-07-16,27.125,93.875,4.371428571428571,999.875 1295 | 2016-07-17,28.125,86.5625,6.6000000000000005,1001.3333333333334 1296 | 2016-07-18,27.666666666666668,91.26666666666667,7.653333333333333,1000.8 1297 | 2016-07-19,32.3125,69.125,13.656249999999998,999.875 1298 | 2016-07-20,34.1875,59.5625,12.9625,999.625 1299 | 2016-07-21,34.13333333333333,56.86666666666667,11.479999999999999,999.2666666666667 1300 | 2016-07-22,34.125,56.125,8.687500000000002,999.3333333333334 1301 | 2016-07-23,31.875,66.0,7.768750000000001,1001.625 1302 | 2016-07-24,31.4375,72.25,2.43125,946.3125 1303 | 2016-07-25,31.9375,69.125,2.9125000000000005,1001.875 1304 | 2016-07-26,30.3125,82.0,6.485714285714287,1003.125 1305 | 2016-07-27,28.3125,88.25,4.293750000000001,1004.625 1306 | 2016-07-28,29.533333333333335,81.33333333333333,5.186666666666667,1002.0666666666667 1307 | 2016-07-29,27.375,92.4375,2.43125,1000.9375 1308 | 2016-07-30,27.333333333333332,92.26666666666667,1.8599999999999999,1001.2 1309 | 2016-07-31,29.266666666666666,83.13333333333334,4.7,1000.6666666666666 1310 | 2016-08-01,29.125,84.25,5.9125000000000005,999.125 1311 | 2016-08-02,30.6875,72.125,6.375,310.4375 1312 | 2016-08-03,32.5625,63.5625,5.45,998.5 1313 | 2016-08-04,33.111111111111114,63.888888888888886,7.927777777777779,998.7777777777778 1314 | 2016-08-05,33.8,61.666666666666664,4.319999999999999,999.3333333333334 1315 | 2016-08-06,30.066666666666666,76.0,6.926666666666667,1000.8 1316 | 2016-08-07,33.11764705882353,65.17647058823529,7.852941176470587,1000.5294117647059 1317 | 2016-08-08,33.80952380952381,62.38095238095238,9.440909090909091,999.6363636363636 1318 | 2016-08-09,31.615384615384617,71.65384615384616,9.549999999999999,998.5 1319 | 2016-08-10,32.0,71.25925925925925,10.159259259259262,999.3703703703703 1320 | 2016-08-11,28.107142857142858,89.46428571428571,7.82142857142857,1001.6071428571429 1321 | 2016-08-12,29.035714285714285,85.0,3.848148148148149,1002.2857142857143 1322 | 2016-08-13,30.321428571428573,77.07142857142857,3.2296296296296303,1001.0714285714286 1323 | 2016-08-14,28.933333333333334,83.7,6.369999999999998,633.9 1324 | 2016-08-15,31.678571428571427,67.64285714285714,12.725,999.2857142857143 1325 | 2016-08-16,31.333333333333332,66.41666666666667,14.66666666666667,-3.0416666666666665 1326 | 2016-08-17,29.928571428571427,72.5,6.325,999.5185185185185 1327 | 2016-08-18,29.88888888888889,79.81481481481481,1.7222222222222225,999.9230769230769 1328 | 2016-08-19,32.07142857142857,68.28571428571429,1.917857142857143,999.8928571428571 1329 | 2016-08-20,33.18518518518518,66.48148148148148,10.366666666666667,1000.16 1330 | 2016-08-21,31.59259259259259,68.07407407407408,10.022222222222224,1001.1923076923077 1331 | 2016-08-22,32.18518518518518,65.55555555555556,8.237037037037037,1002.8888888888889 1332 | 2016-08-23,31.48,70.08,6.084,1003.24 1333 | 2016-08-24,30.178571428571427,79.28571428571429,8.419230769230769,1002.5 1334 | 2016-08-25,31.52,72.44,6.276923076923075,1004.4230769230769 1335 | 2016-08-26,31.22222222222222,73.81481481481481,4.392592592592593,1005.2222222222222 1336 | 2016-08-27,31.785714285714285,70.28571428571429,7.1499999999999995,1006.1428571428571 1337 | 2016-08-28,33.4,66.24,7.2719999999999985,1006.2 1338 | 2016-08-29,29.571428571428573,82.32142857142857,5.825,1003.6071428571429 1339 | 2016-08-30,30.04,76.8,8.903846153846153,1002.1923076923077 1340 | 2016-08-31,27.25925925925926,89.55555555555556,6.729629629629631,1003.0370370370371 1341 | 2016-09-01,27.96,87.24,4.148,1002.44 1342 | 2016-09-02,30.73913043478261,71.73913043478261,13.204347826086956,1001.2727272727273 1343 | 2016-09-03,30.894736842105264,62.68421052631579,13.263157894736842,1001.1578947368421 1344 | 2016-09-04,31.692307692307693,59.30769230769231,11.12307692307692,1002.9230769230769 1345 | 2016-09-05,31.076923076923077,60.30769230769231,13.961538461538463,1003.2307692307693 1346 | 2016-09-06,30.375,55.0,13.437499999999998,1003.75 1347 | 2016-09-07,31.1,57.7,9.1,1003.4545454545455 1348 | 2016-09-08,31.916666666666668,58.0,10.808333333333332,1005.1666666666666 1349 | 2016-09-09,30.555555555555557,56.666666666666664,11.52222222222222,1004.3333333333334 1350 | 2016-09-10,31.23076923076923,58.23076923076923,11.969230769230771,1005.2307692307693 1351 | 2016-09-11,31.0,56.4,8.15,1003.8 1352 | 2016-09-12,31.642857142857142,55.5,10.85,1005.0714285714286 1353 | 2016-09-13,32.53333333333333,57.06666666666667,8.526666666666666,1006.4 1354 | 2016-09-14,30.857142857142858,70.35714285714286,4.235714285714287,1006.9285714285714 1355 | 2016-09-15,31.727272727272727,59.63636363636363,11.9,1004.75 1356 | 2016-09-16,31.4,58.0,3.5200000000000005,1004.4 1357 | 2016-09-17,32.30769230769231,58.0,2.707692307692308,1003.1538461538462 1358 | 2016-09-18,32.25,58.833333333333336,17.908333333333335,1005.0 1359 | 2016-09-19,32.375,59.458333333333336,2.0875,1004.7083333333334 1360 | 2016-09-20,33.44444444444444,54.074074074074076,2.56923076923077,1003.5185185185185 1361 | 2016-09-21,33.36,58.96,6.0920000000000005,1005.4 1362 | 2016-09-22,30.037037037037038,67.62962962962963,8.107407407407404,1008.7777777777778 1363 | 2016-09-23,31.0,66.10714285714286,3.571428571428573,1005.3214285714286 1364 | 2016-09-24,31.24,66.48,5.496153846153845,1352.6153846153845 1365 | 2016-09-25,31.130434782608695,62.30434782608695,3.1478260869565218,1004.7826086956521 1366 | 2016-09-26,31.48,60.88,2.986956521739131,1006.76 1367 | 2016-09-27,32.18518518518518,57.81481481481482,1.7807692307692307,1006.7037037037037 1368 | 2016-09-28,32.44,55.28,2.5200000000000005,1005.28 1369 | 2016-09-29,32.22727272727273,61.31818181818182,3.9636363636363643,1005.5909090909091 1370 | 2016-09-30,32.214285714285715,59.357142857142854,2.582142857142858,1005.5714285714286 1371 | 2016-10-01,32.541666666666664,61.916666666666664,3.170833333333334,1007.5 1372 | 2016-10-02,32.81481481481482,61.81481481481482,4.122222222222223,1009.1111111111111 1373 | 2016-10-03,33.26923076923077,59.57692307692308,3.992592592592593,1007.1851851851852 1374 | 2016-10-04,30.555555555555557,69.81481481481481,5.770370370370371,1005.3703703703703 1375 | 2016-10-05,28.833333333333332,71.875,3.3249999999999997,1005.25 1376 | 2016-10-06,30.703703703703702,64.81481481481481,3.2333333333333325,1007.1481481481482 1377 | 2016-10-07,30.96,59.96,15.825000000000001,1007.2 1378 | 2016-10-08,30.6,57.96,8.76153846153846,1006.8461538461538 1379 | 2016-10-09,30.92,59.2,3.184000000000001,1005.96 1380 | 2016-10-10,29.77777777777778,51.111111111111114,4.807407407407408,1006.1481481481482 1381 | 2016-10-11,29.666666666666668,51.148148148148145,8.922222222222224,1006.5925925925926 1382 | 2016-10-12,29.571428571428573,47.285714285714285,11.371428571428568,1006.25 1383 | 2016-10-13,29.962962962962962,44.0,13.022222222222224,1008.3333333333334 1384 | 2016-10-14,29.75,47.875,8.934782608695652,1009.125 1385 | 2016-10-15,27.74074074074074,54.148148148148145,6.662962962962963,1010.5185185185185 1386 | 2016-10-16,28.428571428571427,49.857142857142854,7.014285714285715,1011.8571428571429 1387 | 2016-10-17,28.6,49.24,7.1160000000000005,1010.56 1388 | 2016-10-18,28.5,48.458333333333336,7.668181818181818,1009.4583333333334 1389 | 2016-10-19,28.925925925925927,49.18518518518518,6.459259259259259,1008.5555555555555 1390 | 2016-10-20,29.076923076923077,47.73076923076923,9.123999999999999,1007.5384615384615 1391 | 2016-10-21,28.40909090909091,50.40909090909091,10.695454545454547,1008.7272727272727 1392 | 2016-10-22,29.333333333333332,49.48148148148148,13.323076923076924,1009.3703703703703 1393 | 2016-10-23,27.5,54.96153846153846,11.546153846153846,1009.3076923076923 1394 | 2016-10-24,28.5,51.42307692307692,11.188461538461537,1008.2692307692307 1395 | 2016-10-25,28.04,54.56,12.388461538461542,1008.9230769230769 1396 | 2016-10-26,27.576923076923077,56.19230769230769,5.699999999999998,1011.7307692307693 1397 | 2016-10-27,26.555555555555557,59.370370370370374,6.044444444444444,1014.6296296296297 1398 | 2016-10-28,25.51851851851852,58.77777777777778,7.277777777777777,1012.4074074074074 1399 | 2016-10-29,25.814814814814813,62.77777777777778,1.7148148148148148,1013.0 1400 | 2016-10-30,24.82608695652174,71.04347826086956,2.0130434782608697,1014.6086956521739 1401 | 2016-10-31,24.53846153846154,66.8076923076923,1.6423076923076925,1017.8076923076923 1402 | 2016-11-01,24.384615384615383,58.73076923076923,2.7153846153846155,1016.3846153846154 1403 | 2016-11-02,23.727272727272727,65.9090909090909,1.4318181818181817,1016.5909090909091 1404 | 2016-11-03,25.64,59.76,2.2320000000000007,1016.24 1405 | 2016-11-04,24.814814814814813,58.2962962962963,4.122222222222222,1012.6296296296297 1406 | 2016-11-05,23.115384615384617,66.57692307692308,5.784615384615384,1010.3461538461538 1407 | 2016-11-06,22.925925925925927,63.851851851851855,2.411111111111111,1011.5185185185185 1408 | 2016-11-07,24.545454545454547,51.31818181818182,8.172727272727274,1012.7727272727273 1409 | 2016-11-08,23.73076923076923,52.26923076923077,12.750000000000002,1013.64 1410 | 2016-11-09,23.0,49.7037037037037,7.074074074074074,1014.6666666666666 1411 | 2016-11-10,23.51851851851852,50.148148148148145,3.225925925925927,1013.8148148148148 1412 | 2016-11-11,23.92,54.04,2.968,1014.08 1413 | 2016-11-12,23.53846153846154,58.03846153846154,2.7846153846153854,1015.1538461538462 1414 | 2016-11-13,24.296296296296298,55.81481481481482,4.3999999999999995,1016.5185185185185 1415 | 2016-11-14,23.346153846153847,60.73076923076923,1.4961538461538462,1015.7307692307693 1416 | 2016-11-15,22.24,55.72,4.087999999999999,1016.24 1417 | 2016-11-16,21.76923076923077,53.23076923076923,7.138461538461537,1017.5384615384615 1418 | 2016-11-17,21.73076923076923,51.73076923076923,11.549999999999999,1350.2962962962963 1419 | 2016-11-18,21.73076923076923,53.61538461538461,7.7799999999999985,1014.6923076923077 1420 | 2016-11-19,20.666666666666668,60.666666666666664,2.8400000000000003,1012.4666666666667 1421 | 2016-11-20,22.25,59.9,2.5050000000000003,1013.35 1422 | 2016-11-21,21.53846153846154,60.46153846153846,2.7153846153846155,1013.0 1423 | 2016-11-22,22.57894736842105,50.1578947368421,7.999999999999998,1012.8947368421053 1424 | 2016-11-23,22.82608695652174,51.43478260869565,6.604347826086956,1013.1739130434783 1425 | 2016-11-24,21.42105263157895,51.473684210526315,8.289473684210526,1014.9473684210526 1426 | 2016-11-25,23.6,30.55,15.085000000000003,1012.95 1427 | 2016-11-26,24.294117647058822,32.64705882352941,14.494117647058824,1013.25 1428 | 2016-11-27,23.636363636363637,33.63636363636363,12.45909090909091,1013.5909090909091 1429 | 2016-11-28,22.454545454545453,37.31818181818182,9.185714285714287,12.045454545454545 1430 | 2016-11-29,21.61111111111111,53.72222222222222,1.238888888888889,1015.0 1431 | 2016-11-30,19.869565217391305,80.08695652173913,4.269565217391306,1016.6086956521739 1432 | 2016-12-01,19.75,84.0,2.704166666666667,1016.875 1433 | 2016-12-02,19.208333333333332,75.875,4.945833333333334,1017.75 1434 | 2016-12-03,21.208333333333332,52.166666666666664,5.866666666666666,1019.3333333333334 1435 | 2016-12-04,18.9,55.25,5.666666666666666,1019.7 1436 | 2016-12-05,18.636363636363637,56.59090909090909,4.9523809523809526,1017.0454545454545 1437 | 2016-12-06,18.53846153846154,69.92307692307692,2.5038461538461543,1017.9615384615385 1438 | 2016-12-07,18.25,74.35,0.925,1017.421052631579 1439 | 2016-12-08,16.9,73.3,1.7650000000000001,1016.2 1440 | 2016-12-09,19.416666666666668,68.125,1.3125,1013.4166666666666 1441 | 2016-12-10,16.444444444444443,82.83333333333333,5.355555555555556,1014.0 1442 | 2016-12-11,20.041666666666668,69.58333333333333,4.716666666666667,1013.2916666666666 1443 | 2016-12-12,19.90909090909091,63.86363636363637,3.2818181818181817,1014.1818181818181 1444 | 2016-12-13,19.05,62.35,3.4300000000000006,1015.1 1445 | 2016-12-14,18.555555555555557,58.611111111111114,8.027777777777775,1017.3333333333334 1446 | 2016-12-15,18.166666666666668,56.625,9.879166666666666,1016.6666666666666 1447 | 2016-12-16,15.833333333333334,63.27777777777778,3.9166666666666674,1018.7777777777778 1448 | 2016-12-17,17.5,63.388888888888886,6.731578947368422,1016.9473684210526 1449 | 2016-12-18,16.083333333333332,64.54166666666667,6.420833333333331,1018.0833333333334 1450 | 2016-12-19,17.857142857142858,56.095238095238095,10.414285714285715,1017.4285714285714 1451 | 2016-12-20,19.8,48.53333333333333,15.926666666666673,1015.2 1452 | 2016-12-21,18.05,54.3,19.40476190476191,1015.6190476190476 1453 | 2016-12-22,17.285714285714285,57.857142857142854,6.1809523809523785,1016.1428571428571 1454 | 2016-12-23,15.55,74.7,1.205,1014.25 1455 | 2016-12-24,17.318181818181817,78.63636363636364,5.236363636363636,1011.3181818181819 1456 | 2016-12-25,14.0,94.3,9.084999999999999,1014.35 1457 | 2016-12-26,17.142857142857142,74.85714285714286,8.784210526315787,1016.952380952381 1458 | 2016-12-27,16.85,67.55,8.335,1017.2 1459 | 2016-12-28,17.217391304347824,68.04347826086956,3.547826086956522,1015.5652173913044 1460 | 2016-12-29,15.238095238095237,87.85714285714286,6.0,1016.9047619047619 1461 | 2016-12-30,14.095238095238095,89.66666666666667,6.266666666666667,1017.9047619047619 1462 | 2016-12-31,15.052631578947368,87.0,7.325,1016.1 1463 | 2017-01-01,10.0,100.0,0.0,1016.0 1464 | -------------------------------------------------------------------------------- /tests-integration/test.py: -------------------------------------------------------------------------------- 1 | import psycopg 2 | 3 | conn = psycopg.connect("host=127.0.0.1 port=5432 user=tom password=pencil dbname=public") 4 | conn.autocommit = True 5 | 6 | with conn.cursor() as cur: 7 | cur.execute("SELECT count(*) FROM delhi") 8 | results = cur.fetchone() 9 | assert results[0] == 1462 10 | 11 | with conn.cursor() as cur: 12 | cur.execute("SELECT * FROM delhi ORDER BY date LIMIT 10") 13 | results = cur.fetchall() 14 | assert len(results) == 10 15 | 16 | with conn.cursor() as cur: 17 | cur.execute("SELECT date FROM delhi WHERE meantemp > %s ORDER BY date", [30]) 18 | results = cur.fetchall() 19 | assert len(results) == 527 20 | assert len(results[0]) == 1 21 | print(results[0]) 22 | -------------------------------------------------------------------------------- /tests-integration/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cargo build 6 | ./target/debug/datafusion-postgres-cli --csv delhi:tests-integration/delhiclimate.csv & 7 | PID=$! 8 | sleep 3 9 | python tests-integration/test.py 10 | kill -9 $PID 2>/dev/null 11 | 12 | ./target/debug/datafusion-postgres-cli --parquet all_types:tests-integration/all_types.parquet & 13 | PID=$! 14 | sleep 3 15 | python tests-integration/test_all_types.py 16 | kill -9 $PID 2>/dev/null -------------------------------------------------------------------------------- /tests-integration/test_all_types.py: -------------------------------------------------------------------------------- 1 | import psycopg 2 | from datetime import date, datetime 3 | 4 | conn: psycopg.connection.Connection = psycopg.connect( 5 | "host=127.0.0.1 port=5432 user=tom password=pencil dbname=public" 6 | ) 7 | conn.autocommit = True 8 | 9 | 10 | def data(format: str): 11 | return [ 12 | ( 13 | 1, 14 | 1.0, 15 | "a", 16 | True, 17 | date(2012, 1, 1), 18 | datetime(2012, 1, 1), 19 | [1, None, 2], 20 | [1.0, None, 2.0], 21 | ["a", None, "b"], 22 | [True, None, False], 23 | [date(2012, 1, 1), None, date(2012, 1, 2)], 24 | [datetime(2012, 1, 1), None, datetime(2012, 1, 2)], 25 | ( 26 | (1, 1.0, "a", True, date(2012, 1, 1), datetime(2012, 1, 1)) 27 | if format == "text" 28 | else ( 29 | "1", 30 | "1", 31 | "a", 32 | "t", 33 | "2012-01-01", 34 | "2012-01-01 00:00:00.000000", 35 | ) 36 | ), 37 | ( 38 | [(1, 1.0, "a", True, date(2012, 1, 1), datetime(2012, 1, 1))] 39 | if format == "text" 40 | else [ 41 | ( 42 | "1", 43 | "1", 44 | "a", 45 | "t", 46 | "2012-01-01", 47 | "2012-01-01 00:00:00.000000", 48 | ) 49 | ] 50 | ), 51 | ), 52 | ( 53 | None, 54 | None, 55 | None, 56 | None, 57 | None, 58 | None, 59 | None, 60 | None, 61 | None, 62 | None, 63 | None, 64 | None, 65 | ( 66 | (None, None, None, None, None, None) 67 | if format == "text" 68 | else ("", "", "", "", "", "") 69 | ), 70 | ( 71 | [(None, None, None, None, None, None)] 72 | if format == "text" 73 | else [("", "", "", "", "", "")] 74 | ), 75 | ), 76 | ( 77 | 2, 78 | 2.0, 79 | "b", 80 | False, 81 | date(2012, 1, 2), 82 | datetime(2012, 1, 2), 83 | None, 84 | None, 85 | None, 86 | None, 87 | None, 88 | None, 89 | ( 90 | (2, 2.0, "b", False, date(2012, 1, 2), datetime(2012, 1, 2)) 91 | if format == "text" 92 | else ( 93 | "2", 94 | "2", 95 | "b", 96 | "f", 97 | "2012-01-02", 98 | "2012-01-02 00:00:00.000000", 99 | ) 100 | ), 101 | ( 102 | [(2, 2.0, "b", False, date(2012, 1, 2), datetime(2012, 1, 2))] 103 | if format == "text" 104 | else [ 105 | ( 106 | "2", 107 | "2", 108 | "b", 109 | "f", 110 | "2012-01-02", 111 | "2012-01-02 00:00:00.000000", 112 | ) 113 | ] 114 | ), 115 | ), 116 | ] 117 | 118 | 119 | def assert_select_all(results: list[psycopg.rows.Row], format: str): 120 | expected = data(format) 121 | 122 | assert len(results) == len( 123 | expected 124 | ), f"Expected {len(expected)} rows, got {len(results)}" 125 | 126 | for i, (res_row, exp_row) in enumerate(zip(results, expected)): 127 | assert len(res_row) == len(exp_row), f"Row {i} column count mismatch" 128 | for j, (res_val, exp_val) in enumerate(zip(res_row, exp_row)): 129 | assert ( 130 | res_val == exp_val 131 | ), f"Mismatch at row {i}, column {j}: expected {exp_val}, got {res_val}" 132 | 133 | 134 | with conn.cursor(binary=True) as cur: 135 | cur.execute("SELECT count(*) FROM all_types") 136 | results = cur.fetchone() 137 | assert results[0] == 3 138 | 139 | with conn.cursor(binary=False) as cur: 140 | cur.execute("SELECT count(*) FROM all_types") 141 | results = cur.fetchone() 142 | assert results[0] == 3 143 | 144 | with conn.cursor(binary=True) as cur: 145 | cur.execute("SELECT * FROM all_types") 146 | results = cur.fetchall() 147 | assert_select_all(results, "text") 148 | 149 | with conn.cursor(binary=False) as cur: 150 | cur.execute("SELECT * FROM all_types") 151 | results = cur.fetchall() 152 | assert_select_all(results, "binary") 153 | --------------------------------------------------------------------------------