├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── ci.yaml │ ├── docs.yaml │ └── release.yaml ├── .gitignore ├── .vscode └── extensions.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── RELEASING.md ├── benches ├── README.md └── benchmarks.rs ├── crates ├── gitql-ast │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── expression.rs │ │ ├── format_checker.rs │ │ ├── interval.rs │ │ ├── lib.rs │ │ ├── operator.rs │ │ ├── query.rs │ │ ├── statement.rs │ │ └── types │ │ ├── any.rs │ │ ├── array.rs │ │ ├── base.rs │ │ ├── boolean.rs │ │ ├── composite.rs │ │ ├── date.rs │ │ ├── datetime.rs │ │ ├── dynamic.rs │ │ ├── float.rs │ │ ├── integer.rs │ │ ├── interval.rs │ │ ├── mod.rs │ │ ├── null.rs │ │ ├── optional.rs │ │ ├── range.rs │ │ ├── text.rs │ │ ├── time.rs │ │ ├── undefined.rs │ │ ├── varargs.rs │ │ └── variant.rs ├── gitql-cli │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── arguments.rs │ │ ├── colored_stream.rs │ │ ├── diagnostic_reporter.rs │ │ ├── lib.rs │ │ └── printer │ │ ├── csv_printer.rs │ │ ├── json_printer.rs │ │ ├── mod.rs │ │ ├── table_printer.rs │ │ └── yaml_printer.rs ├── gitql-core │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── combinations_generator.rs │ │ ├── environment.rs │ │ ├── lib.rs │ │ ├── object.rs │ │ ├── schema.rs │ │ ├── signature.rs │ │ ├── types_table.rs │ │ └── values │ │ ├── array.rs │ │ ├── base.rs │ │ ├── boolean.rs │ │ ├── composite.rs │ │ ├── converters.rs │ │ ├── date.rs │ │ ├── datetime.rs │ │ ├── float.rs │ │ ├── integer.rs │ │ ├── interval.rs │ │ ├── mod.rs │ │ ├── null.rs │ │ ├── range.rs │ │ ├── text.rs │ │ └── time.rs ├── gitql-engine │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── data_provider.rs │ │ ├── engine.rs │ │ ├── engine_distinct.rs │ │ ├── engine_evaluator.rs │ │ ├── engine_executor.rs │ │ ├── engine_filter.rs │ │ ├── engine_group.rs │ │ ├── engine_join.rs │ │ ├── engine_ordering.rs │ │ ├── engine_output_into.rs │ │ ├── engine_window_functions.rs │ │ └── lib.rs ├── gitql-parser │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── context.rs │ │ ├── diagnostic.rs │ │ ├── lib.rs │ │ ├── name_generator.rs │ │ ├── name_similarity.rs │ │ ├── parse_cast.rs │ │ ├── parse_comparisons.rs │ │ ├── parse_function_call.rs │ │ ├── parse_interval.rs │ │ ├── parse_into.rs │ │ ├── parse_ordering.rs │ │ ├── parse_type.rs │ │ ├── parser.rs │ │ ├── token.rs │ │ ├── tokenizer.rs │ │ └── type_checker.rs └── gitql-std │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ ├── aggregation.rs │ ├── array │ └── mod.rs │ ├── datetime │ └── mod.rs │ ├── general │ └── mod.rs │ ├── lib.rs │ ├── meta_types.rs │ ├── number │ └── mod.rs │ ├── range │ └── mod.rs │ ├── regex │ └── mod.rs │ ├── standard.rs │ ├── text │ └── mod.rs │ └── window.rs ├── docs ├── assets │ └── gql_logo.svg ├── expression │ ├── access.md │ ├── array.md │ ├── binary.md │ ├── call.md │ ├── case.md │ ├── cast.md │ ├── index.md │ ├── interval.md │ └── unary.md ├── functions │ ├── aggregations.md │ ├── array.md │ ├── comparison.md │ ├── datetime.md │ ├── index.md │ ├── interval.md │ ├── logical.md │ ├── math.md │ ├── other.md │ ├── range.md │ ├── regex.md │ ├── string.md │ └── window.md ├── gitql_functions.md ├── index.md ├── sdk │ ├── assemble.md │ ├── functions.md │ ├── index.md │ ├── provider.md │ ├── schema.md │ ├── types.md │ └── values.md ├── setup.md ├── statement │ ├── do.md │ ├── group_by.md │ ├── having.md │ ├── index.md │ ├── limit_and_offset.md │ ├── order_by.md │ ├── qualify.md │ ├── select.md │ ├── variables.md │ └── where.md └── structure │ ├── tables.md │ └── types.md ├── media ├── gitql_demo.gif └── gitql_logo.svg ├── mkdocs.yml ├── scripts ├── cargo-out-dir ├── sha256.py └── summarize-release.py └── src ├── gitql ├── functions │ ├── commits.rs │ ├── diffs.rs │ └── mod.rs ├── gitql_data_provider.rs ├── gitql_line_editor.rs ├── gitql_schema.rs ├── mod.rs ├── types │ ├── diff_changes.rs │ └── mod.rs └── values │ ├── diff_changes.rs │ └── mod.rs └── main.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: amrdeveloper -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **GQL (please complete the following information):** 26 | - Version [e.g. 1.0.0] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | fmt: 16 | name: Rustfmt 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: dtolnay/rust-toolchain@stable 21 | - run: cargo fmt --all -- --check 22 | 23 | clippy: 24 | name: Clippy 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: dtolnay/rust-toolchain@stable 29 | - run: cargo clippy -- -D warnings 30 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Build and deploy docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - docs/** 9 | - mkdocs.yml 10 | permissions: 11 | contents: write 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-python@v4 18 | with: 19 | python-version: 3.x 20 | - run: pip install mkdocs-material 21 | - run: mkdocs gh-deploy --force 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "rust-lang.rust-analyzer", 4 | ] 5 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | 3 | ### Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ### Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ### Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ### Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ### Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at amr96@programmer.net. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ### Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please provide issue report in the format that we request, EACH DETAIL IS A HUGE HELP. 4 | 5 | Issues that are not following the guidelines, 6 | will be processed as last priority or never or simply closed as invalid. 7 | 8 | ## Contributing Guide 9 | 10 | Please note that PRs are looked only for approved issues. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gitql" 3 | authors = ["AmrDeveloper"] 4 | version = "0.38.0" 5 | edition = "2021" 6 | description = "A SQL like query language to perform queries on .git files" 7 | license = "MIT" 8 | repository = "https://github.com/amrdeveloper/gql/" 9 | documentation = "https://github.com/amrdeveloper/gql" 10 | readme = "README.md" 11 | keywords = ["gitql", "gitql-sdk", "gql", "git", "sql"] 12 | categories = ["command-line-utilities"] 13 | exclude = [".github/**", "docs/**", "media/**", "scripts/**"] 14 | 15 | [workspace] 16 | members = [ 17 | "crates/gitql-core", 18 | "crates/gitql-std", 19 | "crates/gitql-ast", 20 | "crates/gitql-cli", 21 | "crates/gitql-parser", 22 | "crates/gitql-engine", 23 | ] 24 | 25 | [workspace.dependencies] 26 | gix = { version = "0.72.1", default-features = false } 27 | dyn-clone = { version = "1.0.19" } 28 | comfy-table = { version = "7.1.4" } 29 | termcolor = { version = "1.4.1" } 30 | serde_json = { version = "1.0.140" } 31 | csv = { version = "1.3.1" } 32 | yaml-rust = { version = "0.4.5" } 33 | chrono = { version = "0.4.41" } 34 | regex = { version = "1.11.1" } 35 | rand = { version = "0.9.1" } 36 | indexmap = { version = "2.9.0" } 37 | linked-hash-map = { version = "0.5.6" } 38 | uuid = { version = "1.17.0", features = ["v4"] } 39 | 40 | [profile.release] 41 | lto = true 42 | 43 | [dependencies] 44 | gitql-core = { path = "./crates/gitql-core", version = "0.15.0" } 45 | gitql-std = { path = "./crates/gitql-std", version = "0.15.0" } 46 | gitql-ast = { path = "./crates/gitql-ast", version = "0.34.0" } 47 | gitql-parser = { path = "./crates/gitql-parser", version = "0.37.0" } 48 | gitql-engine = { path = "./crates/gitql-engine", version = "0.38.0" } 49 | gitql-cli = { path = "./crates/gitql-cli", version = "0.38.0" } 50 | 51 | gix = { workspace = true, features = ["blob-diff", "max-performance"] } 52 | 53 | lineeditor = "0.4.1" 54 | 55 | [dev-dependencies] 56 | criterion = "0.6.0" 57 | 58 | # Run all benchmarks with `cargo bench` 59 | # Run individual benchmarks like `cargo bench -- ` e.g. `cargo bench -- tokenizer` 60 | [[bench]] 61 | name = "benchmarks" 62 | harness = false 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 - 2025 Amr Hesham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing GitQL Application 2 | 3 | 1. Update the `CHANGELOG.md`: 4 | 2. Update `Cargo.toml` with the new version. 5 | 6 | 7 | 3. Commit 8 | 9 | ``` 10 | $ git commit -am "Prepare version X.Y.X" 11 | ``` 12 | 13 | 4. Tag 14 | 15 | ``` 16 | $ git tag -am "X.Y.Z" X.Y.Z 17 | ``` 18 | 19 | 5. Push! 20 | 21 | ``` 22 | $ git push && git push --tags 23 | ``` 24 | 25 | This will trigger a GitHub Action workflow which will create a GitHub release and 26 | publish to Cargo. 27 | 28 | # Releasing GitQL SDK crate 29 | 30 | 1. Update `Cargo.toml` with the new version. 31 | 2. `cargo publish --allow-dirty` -------------------------------------------------------------------------------- /benches/README.md: -------------------------------------------------------------------------------- 1 |

GQL - Git Query Language


2 | 3 |

4 | 5 |

6 | 7 |

8 | Crates.io 9 | Deps 10 | Release 11 | Docs 12 | GitHub release 13 | GitHub issues 14 | GitHub 15 | GitHub all releases 16 |

17 | 18 | Run all benchmarks with `cargo bench` -------------------------------------------------------------------------------- /benches/benchmarks.rs: -------------------------------------------------------------------------------- 1 | use std::hint::black_box; 2 | 3 | use criterion::criterion_group; 4 | use criterion::criterion_main; 5 | use criterion::Criterion; 6 | use gitql_parser::tokenizer::Tokenizer; 7 | 8 | const QUERY_100_CHAR: &str = "SELECT name, COUNT(name) FROM commits GROUP BY name, author_email ORDER BY commit_num DESC LIMIT 100"; 9 | 10 | fn tokenizer_100_char_benchmark(c: &mut Criterion) { 11 | c.bench_function("Tokenizer 100 Char", |b| { 12 | b.iter(|| Tokenizer::tokenize(black_box(QUERY_100_CHAR.to_owned()))) 13 | }); 14 | } 15 | 16 | fn tokenizer_100k_char_benchmark(c: &mut Criterion) { 17 | let query_100k_char = QUERY_100_CHAR.repeat(100_000 / 100); 18 | c.bench_function("Tokenizer 100K Char", |b| { 19 | b.iter(|| Tokenizer::tokenize(black_box(query_100k_char.to_owned()))) 20 | }); 21 | } 22 | 23 | fn tokenizer_1m_char_benchmark(c: &mut Criterion) { 24 | let query_100k_char = QUERY_100_CHAR.repeat(1_000_000 / 100); 25 | c.bench_function("Tokenizer 1M Char", |b| { 26 | b.iter(|| Tokenizer::tokenize(black_box(query_100k_char.to_owned()))) 27 | }); 28 | } 29 | 30 | fn tokenizer_10m_char_benchmark(c: &mut Criterion) { 31 | let query_100k_char = QUERY_100_CHAR.repeat(10_000_000 / 100); 32 | c.bench_function("Tokenizer 10M Char", |b| { 33 | b.iter(|| Tokenizer::tokenize(black_box(query_100k_char.to_owned()))) 34 | }); 35 | } 36 | 37 | criterion_group! { 38 | name = benches; 39 | config = Criterion::default().significance_level(0.1).sample_size(10); 40 | targets = 41 | // Tokenizer 42 | tokenizer_100_char_benchmark, 43 | tokenizer_100k_char_benchmark, 44 | tokenizer_1m_char_benchmark, 45 | tokenizer_10m_char_benchmark 46 | } 47 | 48 | criterion_main!(benches); 49 | -------------------------------------------------------------------------------- /crates/gitql-ast/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gitql-ast" 3 | authors = ["AmrDeveloper"] 4 | version = "0.34.0" 5 | edition = "2021" 6 | description = "GitQL Abstract syntax tree (AST)" 7 | repository = "https://github.com/amrdeveloper/gql/tree/main/crates/gitql-ast" 8 | license = "MIT" 9 | keywords = ["cli", "gql", "language", "git", "sql"] 10 | categories = ["command-line-utilities"] 11 | 12 | [dependencies] 13 | dyn-clone = { workspace = true } 14 | -------------------------------------------------------------------------------- /crates/gitql-ast/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Amr Hesham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/gitql-ast/README.md: -------------------------------------------------------------------------------- 1 |

GQL - Git Query Language AST


2 | 3 |

4 | Crates.io 5 | Deps 6 | Release 7 | Docs 8 | GitHub release 9 | GitHub issues 10 | GitHub 11 | GitHub all releases 12 |

13 | 14 | 15 | ### License 16 | ``` 17 | MIT License 18 | 19 | Copyright (c) 2023 Amr Hesham 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | ``` 39 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/format_checker.rs: -------------------------------------------------------------------------------- 1 | /// Check if String literal is matching SQL time format: HH:MM:SS or HH:MM:SS.SSS 2 | pub fn is_valid_time_format(time_str: &str) -> bool { 3 | // Check length of the string 4 | if !(8..=12).contains(&time_str.len()) { 5 | return false; 6 | } 7 | 8 | // Split the string into hours, minutes, seconds, and optional milliseconds 9 | let parts: Vec<&str> = time_str.split(':').collect(); 10 | if parts.len() < 3 || parts.len() > 4 { 11 | return false; 12 | } 13 | 14 | // Extract hours, minutes, seconds, and optionally milliseconds 15 | let hours = parts[0].parse::().ok(); 16 | let minutes = parts[1].parse::().ok(); 17 | let seconds_parts: Vec<&str> = parts[2].split('.').collect(); 18 | let seconds = seconds_parts[0].parse::().ok(); 19 | let milliseconds = if seconds_parts.len() == 2 { 20 | seconds_parts[1].parse::().ok() 21 | } else { 22 | Some(0) 23 | }; 24 | 25 | // Validate the parsed values 26 | hours.is_some() 27 | && minutes.is_some() 28 | && seconds.is_some() 29 | && milliseconds.is_some() 30 | && hours.unwrap() < 24 31 | && minutes.unwrap() < 60 32 | && seconds.unwrap() < 60 33 | && milliseconds.unwrap() < 1000 34 | } 35 | 36 | /// Check if String literal is matching SQL Date format: YYYY-MM-DD 37 | pub fn is_valid_date_format(date_str: &str) -> bool { 38 | // Check length of the string 39 | if date_str.len() != 10 { 40 | return false; 41 | } 42 | 43 | // Split the string into year, month, and day 44 | let parts: Vec<&str> = date_str.split('-').collect(); 45 | if parts.len() != 3 { 46 | return false; 47 | } 48 | 49 | // Extract year, month, and day 50 | let year = parts[0].parse::().ok(); 51 | let month = parts[1].parse::().ok(); 52 | let day = parts[2].parse::().ok(); 53 | 54 | // Validate the parsed values 55 | year.is_some() 56 | && month.is_some() 57 | && day.is_some() 58 | && year.unwrap() >= 1 59 | && month.unwrap() >= 1 60 | && month.unwrap() <= 12 61 | && day.unwrap() >= 1 62 | && day.unwrap() <= 31 63 | } 64 | 65 | /// Check if String literal is matching SQL Date format: YYYY-MM-DD HH:MM:SS or YYYY-MM-DD HH:MM:SS.SSS 66 | pub fn is_valid_datetime_format(datetime_str: &str) -> bool { 67 | // Check length of the string 68 | if !(19..=23).contains(&datetime_str.len()) { 69 | return false; 70 | } 71 | 72 | // Split the string into date and time components 73 | let parts: Vec<&str> = datetime_str.split_whitespace().collect(); 74 | if parts.len() != 2 { 75 | return false; 76 | } 77 | 78 | // Check the validity of date and time components 79 | is_valid_date_format(parts[0]) && is_valid_time_format(parts[1]) 80 | } 81 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod expression; 2 | pub mod format_checker; 3 | pub mod operator; 4 | pub mod query; 5 | pub mod statement; 6 | pub mod types; 7 | 8 | mod interval; 9 | pub use interval::Interval; 10 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/operator.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, PartialEq)] 2 | pub enum PrefixUnaryOperator { 3 | Negative, 4 | Bang, 5 | Not, 6 | } 7 | 8 | #[derive(Clone, PartialEq)] 9 | pub enum ArithmeticOperator { 10 | Plus, 11 | Minus, 12 | Star, 13 | Slash, 14 | Modulus, 15 | Exponentiation, 16 | } 17 | 18 | #[derive(Clone, PartialEq)] 19 | pub enum ComparisonOperator { 20 | Greater, 21 | GreaterEqual, 22 | Less, 23 | LessEqual, 24 | Equal, 25 | NotEqual, 26 | NullSafeEqual, 27 | } 28 | 29 | #[derive(Clone, PartialEq)] 30 | pub enum GroupComparisonOperator { 31 | All, 32 | Any, 33 | } 34 | 35 | #[derive(Clone, PartialEq)] 36 | pub enum BinaryLogicalOperator { 37 | Or, 38 | And, 39 | Xor, 40 | } 41 | 42 | #[derive(Clone, PartialEq)] 43 | pub enum BinaryBitwiseOperator { 44 | Or, 45 | And, 46 | Xor, 47 | RightShift, 48 | LeftShift, 49 | } 50 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/query.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::expression::Expr; 4 | use crate::statement::Statement; 5 | 6 | pub enum Query { 7 | Select(SelectQuery), 8 | GlobalVariableDecl(GlobalVariableDeclQuery), 9 | Do(DoQuery), 10 | DescribeTable(DescribeQuery), 11 | ShowTables, 12 | } 13 | 14 | pub struct SelectQuery { 15 | pub statements: HashMap<&'static str, Box>, 16 | pub alias_table: HashMap, 17 | pub has_aggregation_function: bool, 18 | pub has_group_by_statement: bool, 19 | pub hidden_selections: HashMap>, 20 | } 21 | 22 | pub struct DoQuery { 23 | pub exprs: Vec>, 24 | } 25 | 26 | pub struct DescribeQuery { 27 | pub table_name: String, 28 | } 29 | 30 | pub struct GlobalVariableDeclQuery { 31 | pub name: String, 32 | pub value: Box, 33 | } 34 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/any.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::base::DataType; 4 | 5 | #[derive(Clone)] 6 | pub struct AnyType; 7 | 8 | impl DataType for AnyType { 9 | fn literal(&self) -> String { 10 | "Any".to_string() 11 | } 12 | 13 | fn equals(&self, _other: &Box) -> bool { 14 | true 15 | } 16 | 17 | fn as_any(&self) -> &dyn Any { 18 | self 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/array.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::base::DataType; 4 | use super::boolean::BoolType; 5 | use super::integer::IntType; 6 | 7 | #[derive(Clone)] 8 | pub struct ArrayType { 9 | pub base: Box, 10 | } 11 | 12 | impl ArrayType { 13 | pub fn new(base: Box) -> Self { 14 | ArrayType { base } 15 | } 16 | } 17 | 18 | impl DataType for ArrayType { 19 | fn literal(&self) -> String { 20 | format!("Array({})", self.base.literal()) 21 | } 22 | 23 | fn equals(&self, other: &Box) -> bool { 24 | let array_type: Box = Box::new(self.clone()); 25 | if other.is_any() || other.is_variant_contains(&array_type) { 26 | return true; 27 | } 28 | 29 | if let Some(other_array) = other.as_any().downcast_ref::() { 30 | return self.base.equals(&other_array.base); 31 | } 32 | false 33 | } 34 | 35 | fn as_any(&self) -> &dyn Any { 36 | self 37 | } 38 | 39 | fn can_perform_index_op_with(&self) -> Vec> { 40 | vec![Box::new(IntType)] 41 | } 42 | 43 | fn index_op_result_type(&self, _other: &Box) -> Box { 44 | self.base.clone() 45 | } 46 | 47 | fn can_perform_slice_op(&self) -> bool { 48 | true 49 | } 50 | 51 | fn can_perform_slice_op_with(&self) -> Vec> { 52 | vec![Box::new(IntType)] 53 | } 54 | 55 | fn can_perform_contains_op_with(&self) -> Vec> { 56 | vec![Box::new(self.clone()), self.base.clone()] 57 | } 58 | 59 | fn can_perform_logical_or_op_with(&self) -> Vec> { 60 | vec![Box::new(self.clone())] 61 | } 62 | 63 | fn logical_or_op_result_type(&self, _other: &Box) -> Box { 64 | Box::new(BoolType) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/boolean.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::expression::Expr; 4 | use crate::expression::StringExpr; 5 | use crate::types::array::ArrayType; 6 | use crate::types::integer::IntType; 7 | 8 | use super::base::DataType; 9 | 10 | #[derive(Clone)] 11 | pub struct BoolType; 12 | 13 | impl DataType for BoolType { 14 | fn literal(&self) -> String { 15 | "Boolean".to_string() 16 | } 17 | 18 | fn equals(&self, other: &Box) -> bool { 19 | other.is_any() || other.is_bool() || other.is_variant_with(|t| t.is_bool()) 20 | } 21 | 22 | fn as_any(&self) -> &dyn Any { 23 | self 24 | } 25 | 26 | fn can_perform_bang_op(&self) -> bool { 27 | true 28 | } 29 | 30 | fn bang_op_result_type(&self) -> Box { 31 | Box::new(BoolType) 32 | } 33 | 34 | fn can_perform_logical_or_op_with(&self) -> Vec> { 35 | vec![Box::new(BoolType)] 36 | } 37 | 38 | fn logical_or_op_result_type(&self, _other: &Box) -> Box { 39 | Box::new(self.clone()) 40 | } 41 | 42 | fn can_perform_logical_and_op_with(&self) -> Vec> { 43 | vec![Box::new(BoolType)] 44 | } 45 | 46 | fn logical_and_op_result_type(&self, _other: &Box) -> Box { 47 | Box::new(self.clone()) 48 | } 49 | 50 | fn can_perform_logical_xor_op_with(&self) -> Vec> { 51 | vec![Box::new(BoolType)] 52 | } 53 | 54 | fn logical_xor_op_result_type(&self, _other: &Box) -> Box { 55 | Box::new(self.clone()) 56 | } 57 | 58 | fn can_perform_eq_op_with(&self) -> Vec> { 59 | vec![Box::new(BoolType)] 60 | } 61 | 62 | fn can_perform_group_eq_op_with(&self) -> Vec> { 63 | vec![Box::new(ArrayType::new(Box::new(BoolType)))] 64 | } 65 | 66 | fn can_perform_bang_eq_op_with(&self) -> Vec> { 67 | vec![Box::new(BoolType)] 68 | } 69 | 70 | fn can_perform_group_bang_eq_op_with(&self) -> Vec> { 71 | vec![Box::new(ArrayType::new(Box::new(BoolType)))] 72 | } 73 | 74 | fn can_perform_gt_op_with(&self) -> Vec> { 75 | vec![Box::new(BoolType)] 76 | } 77 | 78 | fn can_perform_group_gt_op_with(&self) -> Vec> { 79 | vec![Box::new(ArrayType::new(Box::new(BoolType)))] 80 | } 81 | 82 | fn can_perform_gte_op_with(&self) -> Vec> { 83 | vec![Box::new(BoolType)] 84 | } 85 | 86 | fn can_perform_group_gte_op_with(&self) -> Vec> { 87 | vec![Box::new(ArrayType::new(Box::new(BoolType)))] 88 | } 89 | 90 | fn can_perform_lt_op_with(&self) -> Vec> { 91 | vec![Box::new(BoolType)] 92 | } 93 | 94 | fn can_perform_group_lt_op_with(&self) -> Vec> { 95 | vec![Box::new(ArrayType::new(Box::new(BoolType)))] 96 | } 97 | 98 | fn can_perform_lte_op_with(&self) -> Vec> { 99 | vec![Box::new(BoolType)] 100 | } 101 | 102 | fn can_perform_group_lte_op_with(&self) -> Vec> { 103 | vec![Box::new(ArrayType::new(Box::new(BoolType)))] 104 | } 105 | 106 | fn not_op_result_type(&self) -> Box { 107 | Box::new(self.clone()) 108 | } 109 | 110 | fn has_implicit_cast_from(&self, expr: &Box) -> bool { 111 | if let Some(string_expr) = expr.as_any().downcast_ref::() { 112 | const BOOLEANS_VALUES_LITERAL: [&str; 10] = 113 | ["t", "true", "y", "yes", "1", "f", "false", "n", "no", "0"]; 114 | return BOOLEANS_VALUES_LITERAL.contains(&string_expr.value.as_str()); 115 | } 116 | false 117 | } 118 | 119 | fn can_perform_explicit_cast_op_to(&self) -> Vec> { 120 | vec![Box::new(IntType)] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/composite.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::collections::HashMap; 3 | 4 | use super::base::DataType; 5 | 6 | #[derive(Clone)] 7 | pub struct CompositeType { 8 | pub name: String, 9 | pub members: HashMap>, 10 | } 11 | 12 | impl CompositeType { 13 | pub fn new(name: String, members: HashMap>) -> Self { 14 | CompositeType { name, members } 15 | } 16 | 17 | pub fn empty(name: String) -> Self { 18 | CompositeType { 19 | name, 20 | members: HashMap::default(), 21 | } 22 | } 23 | 24 | pub fn add_member(mut self, name: String, data_type: Box) -> Self { 25 | self.members.insert(name, data_type); 26 | self 27 | } 28 | } 29 | 30 | impl DataType for CompositeType { 31 | fn literal(&self) -> String { 32 | self.name.to_string() 33 | } 34 | 35 | fn equals(&self, other: &Box) -> bool { 36 | if other.is_any() { 37 | return true; 38 | } 39 | 40 | let composite_type: Box = Box::new(self.clone()); 41 | if other.is_variant_contains(&composite_type) { 42 | return true; 43 | } 44 | 45 | if let Some(other_composite) = other.as_any().downcast_ref::() { 46 | if self.name.ne(&other_composite.name) { 47 | return false; 48 | } 49 | 50 | return self.members.eq(&other_composite.members); 51 | } 52 | 53 | false 54 | } 55 | 56 | fn as_any(&self) -> &dyn Any { 57 | self 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/date.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::expression::Expr; 4 | use crate::expression::StringExpr; 5 | use crate::format_checker::is_valid_date_format; 6 | use crate::types::array::ArrayType; 7 | use crate::types::datetime::DateTimeType; 8 | use crate::types::integer::IntType; 9 | 10 | use super::base::DataType; 11 | 12 | #[derive(Clone)] 13 | pub struct DateType; 14 | 15 | impl DataType for DateType { 16 | fn literal(&self) -> String { 17 | "Date".to_string() 18 | } 19 | 20 | fn equals(&self, other: &Box) -> bool { 21 | other.is_any() || other.is_date() || other.is_variant_with(|t| t.is_date()) 22 | } 23 | 24 | fn as_any(&self) -> &dyn Any { 25 | self 26 | } 27 | 28 | fn can_perform_add_op_with(&self) -> Vec> { 29 | vec![Box::new(IntType)] 30 | } 31 | 32 | fn add_op_result_type(&self, _other: &Box) -> Box { 33 | Box::new(DateType) 34 | } 35 | 36 | fn can_perform_sub_op_with(&self) -> Vec> { 37 | vec![Box::new(IntType)] 38 | } 39 | 40 | fn sub_op_result_type(&self, _other: &Box) -> Box { 41 | Box::new(DateType) 42 | } 43 | 44 | fn can_perform_eq_op_with(&self) -> Vec> { 45 | vec![Box::new(DateType)] 46 | } 47 | 48 | fn can_perform_group_eq_op_with(&self) -> Vec> { 49 | vec![Box::new(ArrayType::new(Box::new(DateType)))] 50 | } 51 | 52 | fn can_perform_bang_eq_op_with(&self) -> Vec> { 53 | vec![Box::new(DateType)] 54 | } 55 | 56 | fn can_perform_group_bang_eq_op_with(&self) -> Vec> { 57 | vec![Box::new(ArrayType::new(Box::new(DateType)))] 58 | } 59 | 60 | fn can_perform_gt_op_with(&self) -> Vec> { 61 | vec![Box::new(DateType)] 62 | } 63 | 64 | fn can_perform_group_gt_op_with(&self) -> Vec> { 65 | vec![Box::new(ArrayType::new(Box::new(DateType)))] 66 | } 67 | 68 | fn can_perform_gte_op_with(&self) -> Vec> { 69 | vec![Box::new(DateType)] 70 | } 71 | 72 | fn can_perform_group_gte_op_with(&self) -> Vec> { 73 | vec![Box::new(ArrayType::new(Box::new(DateType)))] 74 | } 75 | 76 | fn can_perform_lt_op_with(&self) -> Vec> { 77 | vec![Box::new(DateType)] 78 | } 79 | 80 | fn can_perform_group_lt_op_with(&self) -> Vec> { 81 | vec![Box::new(ArrayType::new(Box::new(DateType)))] 82 | } 83 | 84 | fn can_perform_lte_op_with(&self) -> Vec> { 85 | vec![Box::new(DateType)] 86 | } 87 | 88 | fn can_perform_group_lte_op_with(&self) -> Vec> { 89 | vec![Box::new(ArrayType::new(Box::new(DateType)))] 90 | } 91 | 92 | fn has_implicit_cast_from(&self, expr: &Box) -> bool { 93 | if let Some(string_expr) = expr.as_any().downcast_ref::() { 94 | return is_valid_date_format(&string_expr.value); 95 | } 96 | false 97 | } 98 | 99 | fn can_perform_explicit_cast_op_to(&self) -> Vec> { 100 | vec![Box::new(DateTimeType)] 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/datetime.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::expression::Expr; 4 | use crate::expression::StringExpr; 5 | use crate::format_checker::is_valid_datetime_format; 6 | use crate::types::array::ArrayType; 7 | use crate::types::date::DateType; 8 | 9 | use super::base::DataType; 10 | 11 | #[derive(Clone)] 12 | pub struct DateTimeType; 13 | 14 | impl DataType for DateTimeType { 15 | fn literal(&self) -> String { 16 | "DateTime".to_string() 17 | } 18 | 19 | fn equals(&self, other: &Box) -> bool { 20 | other.is_any() || other.is_date_time() || other.is_variant_with(|t| t.is_date_time()) 21 | } 22 | 23 | fn as_any(&self) -> &dyn Any { 24 | self 25 | } 26 | 27 | fn can_perform_eq_op_with(&self) -> Vec> { 28 | vec![Box::new(DateTimeType)] 29 | } 30 | 31 | fn can_perform_group_eq_op_with(&self) -> Vec> { 32 | vec![Box::new(ArrayType::new(Box::new(DateTimeType)))] 33 | } 34 | 35 | fn can_perform_bang_eq_op_with(&self) -> Vec> { 36 | vec![Box::new(DateTimeType)] 37 | } 38 | 39 | fn can_perform_group_bang_eq_op_with(&self) -> Vec> { 40 | vec![Box::new(ArrayType::new(Box::new(DateTimeType)))] 41 | } 42 | 43 | fn can_perform_gt_op_with(&self) -> Vec> { 44 | vec![Box::new(DateTimeType)] 45 | } 46 | 47 | fn can_perform_group_gt_op_with(&self) -> Vec> { 48 | vec![Box::new(ArrayType::new(Box::new(DateTimeType)))] 49 | } 50 | 51 | fn can_perform_gte_op_with(&self) -> Vec> { 52 | vec![Box::new(DateTimeType)] 53 | } 54 | 55 | fn can_perform_group_gte_op_with(&self) -> Vec> { 56 | vec![Box::new(ArrayType::new(Box::new(DateTimeType)))] 57 | } 58 | 59 | fn can_perform_lt_op_with(&self) -> Vec> { 60 | vec![Box::new(DateTimeType)] 61 | } 62 | 63 | fn can_perform_group_lt_op_with(&self) -> Vec> { 64 | vec![Box::new(ArrayType::new(Box::new(DateTimeType)))] 65 | } 66 | 67 | fn can_perform_lte_op_with(&self) -> Vec> { 68 | vec![Box::new(DateTimeType)] 69 | } 70 | 71 | fn can_perform_group_lte_op_with(&self) -> Vec> { 72 | vec![Box::new(ArrayType::new(Box::new(DateTimeType)))] 73 | } 74 | 75 | fn has_implicit_cast_from(&self, expr: &Box) -> bool { 76 | if let Some(string_expr) = expr.as_any().downcast_ref::() { 77 | return is_valid_datetime_format(&string_expr.value); 78 | } 79 | false 80 | } 81 | 82 | fn can_perform_explicit_cast_op_to(&self) -> Vec> { 83 | vec![Box::new(DateType)] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/dynamic.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::base::DataType; 4 | 5 | /// A function that resolve a dynamic type depending on a list of already resolved types 6 | pub type ResolveFunction = fn(&[Box]) -> Box; 7 | 8 | #[derive(Clone)] 9 | #[allow(clippy::borrowed_box)] 10 | pub struct DynamicType { 11 | pub function: ResolveFunction, 12 | } 13 | 14 | impl DynamicType { 15 | #[allow(clippy::type_complexity)] 16 | pub fn new(function: ResolveFunction) -> Self { 17 | DynamicType { function } 18 | } 19 | } 20 | 21 | impl DataType for DynamicType { 22 | fn literal(&self) -> String { 23 | "Dynamic".to_string() 24 | } 25 | 26 | fn equals(&self, _other: &Box) -> bool { 27 | false 28 | } 29 | 30 | fn as_any(&self) -> &dyn Any { 31 | self 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/float.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::types::{array::ArrayType, integer::IntType}; 4 | 5 | use super::base::DataType; 6 | 7 | #[derive(Clone)] 8 | pub struct FloatType; 9 | 10 | impl DataType for FloatType { 11 | fn literal(&self) -> String { 12 | "Float".to_string() 13 | } 14 | 15 | fn equals(&self, other: &Box) -> bool { 16 | other.is_any() || other.is_float() || other.is_variant_with(|t| t.is_float()) 17 | } 18 | 19 | fn as_any(&self) -> &dyn Any { 20 | self 21 | } 22 | 23 | fn can_perform_add_op_with(&self) -> Vec> { 24 | vec![Box::new(FloatType)] 25 | } 26 | 27 | fn add_op_result_type(&self, _other: &Box) -> Box { 28 | Box::new(FloatType) 29 | } 30 | 31 | fn can_perform_sub_op_with(&self) -> Vec> { 32 | vec![Box::new(FloatType)] 33 | } 34 | 35 | fn sub_op_result_type(&self, _other: &Box) -> Box { 36 | Box::new(FloatType) 37 | } 38 | 39 | fn can_perform_mul_op_with(&self) -> Vec> { 40 | vec![Box::new(FloatType)] 41 | } 42 | 43 | fn mul_op_result_type(&self, _other: &Box) -> Box { 44 | Box::new(FloatType) 45 | } 46 | 47 | fn can_perform_div_op_with(&self) -> Vec> { 48 | vec![Box::new(FloatType)] 49 | } 50 | 51 | fn div_op_result_type(&self, _other: &Box) -> Box { 52 | Box::new(FloatType) 53 | } 54 | 55 | fn can_perform_eq_op_with(&self) -> Vec> { 56 | vec![Box::new(FloatType)] 57 | } 58 | 59 | fn can_perform_group_eq_op_with(&self) -> Vec> { 60 | vec![Box::new(ArrayType::new(Box::new(FloatType)))] 61 | } 62 | 63 | fn can_perform_bang_eq_op_with(&self) -> Vec> { 64 | vec![Box::new(FloatType)] 65 | } 66 | 67 | fn can_perform_group_bang_eq_op_with(&self) -> Vec> { 68 | vec![Box::new(ArrayType::new(Box::new(FloatType)))] 69 | } 70 | 71 | fn can_perform_gt_op_with(&self) -> Vec> { 72 | vec![Box::new(FloatType)] 73 | } 74 | 75 | fn can_perform_group_gt_op_with(&self) -> Vec> { 76 | vec![Box::new(ArrayType::new(Box::new(FloatType)))] 77 | } 78 | 79 | fn can_perform_gte_op_with(&self) -> Vec> { 80 | vec![Box::new(FloatType)] 81 | } 82 | 83 | fn can_perform_group_gte_op_with(&self) -> Vec> { 84 | vec![Box::new(ArrayType::new(Box::new(FloatType)))] 85 | } 86 | 87 | fn can_perform_lt_op_with(&self) -> Vec> { 88 | vec![Box::new(FloatType)] 89 | } 90 | 91 | fn can_perform_group_lt_op_with(&self) -> Vec> { 92 | vec![Box::new(ArrayType::new(Box::new(FloatType)))] 93 | } 94 | 95 | fn can_perform_lte_op_with(&self) -> Vec> { 96 | vec![Box::new(FloatType)] 97 | } 98 | 99 | fn can_perform_group_lte_op_with(&self) -> Vec> { 100 | vec![Box::new(ArrayType::new(Box::new(FloatType)))] 101 | } 102 | 103 | fn can_perform_neg_op(&self) -> bool { 104 | true 105 | } 106 | 107 | fn neg_op_result_type(&self) -> Box { 108 | Box::new(self.clone()) 109 | } 110 | 111 | fn can_perform_explicit_cast_op_to(&self) -> Vec> { 112 | vec![Box::new(IntType)] 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/interval.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::types::integer::IntType; 4 | 5 | use super::base::DataType; 6 | 7 | #[derive(Clone)] 8 | pub struct IntervalType; 9 | 10 | impl DataType for IntervalType { 11 | fn literal(&self) -> String { 12 | "Interval".to_string() 13 | } 14 | 15 | fn equals(&self, other: &Box) -> bool { 16 | other.is_any() || other.is_interval() || other.is_variant_with(|t| t.is_interval()) 17 | } 18 | 19 | fn as_any(&self) -> &dyn Any { 20 | self 21 | } 22 | 23 | fn can_perform_add_op_with(&self) -> Vec> { 24 | vec![Box::new(IntervalType)] 25 | } 26 | 27 | fn add_op_result_type(&self, _other: &Box) -> Box { 28 | Box::new(IntervalType) 29 | } 30 | 31 | fn can_perform_sub_op_with(&self) -> Vec> { 32 | vec![Box::new(IntervalType)] 33 | } 34 | 35 | fn sub_op_result_type(&self, _other: &Box) -> Box { 36 | Box::new(IntervalType) 37 | } 38 | 39 | fn can_perform_mul_op_with(&self) -> Vec> { 40 | vec![Box::new(IntType)] 41 | } 42 | 43 | fn mul_op_result_type(&self, _other: &Box) -> Box { 44 | Box::new(IntervalType) 45 | } 46 | 47 | fn can_perform_div_op_with(&self) -> Vec> { 48 | vec![Box::new(IntType)] 49 | } 50 | 51 | fn div_op_result_type(&self, _other: &Box) -> Box { 52 | Box::new(IntervalType) 53 | } 54 | 55 | fn can_perform_eq_op_with(&self) -> Vec> { 56 | vec![Box::new(IntervalType)] 57 | } 58 | 59 | fn can_perform_bang_eq_op_with(&self) -> Vec> { 60 | vec![Box::new(IntervalType)] 61 | } 62 | 63 | fn can_perform_gt_op_with(&self) -> Vec> { 64 | vec![Box::new(IntervalType)] 65 | } 66 | 67 | fn can_perform_gte_op_with(&self) -> Vec> { 68 | vec![Box::new(IntervalType)] 69 | } 70 | 71 | fn can_perform_lt_op_with(&self) -> Vec> { 72 | vec![Box::new(IntervalType)] 73 | } 74 | 75 | fn can_perform_lte_op_with(&self) -> Vec> { 76 | vec![Box::new(IntervalType)] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod any; 2 | pub mod array; 3 | pub mod boolean; 4 | pub mod composite; 5 | pub mod date; 6 | pub mod datetime; 7 | pub mod dynamic; 8 | pub mod float; 9 | pub mod integer; 10 | pub mod interval; 11 | pub mod null; 12 | pub mod optional; 13 | pub mod range; 14 | pub mod text; 15 | pub mod time; 16 | pub mod undefined; 17 | pub mod varargs; 18 | pub mod variant; 19 | 20 | mod base; 21 | pub use base::DataType; 22 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/null.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::base::DataType; 4 | 5 | #[derive(Clone)] 6 | pub struct NullType; 7 | 8 | impl DataType for NullType { 9 | fn literal(&self) -> String { 10 | "Null".to_string() 11 | } 12 | 13 | fn equals(&self, other: &Box) -> bool { 14 | other.is_any() || other.is_null() || other.is_variant_with(|t| t.is_null()) 15 | } 16 | 17 | fn as_any(&self) -> &dyn Any { 18 | self 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/optional.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::base::DataType; 4 | 5 | #[derive(Clone)] 6 | pub struct OptionType { 7 | pub base: Option>, 8 | } 9 | 10 | impl OptionType { 11 | pub fn new(base: Option>) -> Self { 12 | OptionType { base } 13 | } 14 | } 15 | 16 | impl DataType for OptionType { 17 | fn literal(&self) -> String { 18 | if let Some(base) = &self.base { 19 | return format!("{}?", base.literal()); 20 | } 21 | "None".to_string() 22 | } 23 | 24 | fn equals(&self, other: &Box) -> bool { 25 | if other.is_any() { 26 | return true; 27 | } 28 | if let Some(base) = &self.base { 29 | return base.equals(other); 30 | } 31 | true 32 | } 33 | 34 | fn as_any(&self) -> &dyn Any { 35 | self 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/range.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::base::DataType; 4 | use super::boolean::BoolType; 5 | 6 | #[derive(Clone)] 7 | pub struct RangeType { 8 | pub base: Box, 9 | } 10 | 11 | impl RangeType { 12 | pub fn new(base: Box) -> Self { 13 | RangeType { base } 14 | } 15 | } 16 | 17 | impl DataType for RangeType { 18 | fn literal(&self) -> String { 19 | format!("Range({})", self.base.literal()) 20 | } 21 | 22 | fn equals(&self, other: &Box) -> bool { 23 | let range_type: Box = Box::new(self.clone()); 24 | if other.is_any() || other.is_variant_contains(&range_type) { 25 | return true; 26 | } 27 | 28 | if let Some(other_range) = other.as_any().downcast_ref::() { 29 | return self.base.equals(&other_range.base); 30 | } 31 | false 32 | } 33 | 34 | fn as_any(&self) -> &dyn Any { 35 | self 36 | } 37 | 38 | fn can_perform_contains_op_with(&self) -> Vec> { 39 | vec![Box::new(self.clone()), self.base.clone()] 40 | } 41 | 42 | fn can_perform_logical_or_op_with(&self) -> Vec> { 43 | vec![Box::new(self.clone())] 44 | } 45 | 46 | fn logical_or_op_result_type(&self, _other: &Box) -> Box { 47 | Box::new(BoolType) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/text.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::types::array::ArrayType; 4 | 5 | use super::base::DataType; 6 | 7 | #[derive(Clone)] 8 | pub struct TextType; 9 | 10 | impl DataType for TextType { 11 | fn literal(&self) -> String { 12 | "Text".to_string() 13 | } 14 | 15 | fn equals(&self, other: &Box) -> bool { 16 | other.is_any() || other.is_text() || other.is_variant_with(|t| t.is_text()) 17 | } 18 | 19 | fn as_any(&self) -> &dyn Any { 20 | self 21 | } 22 | 23 | fn can_perform_eq_op_with(&self) -> Vec> { 24 | vec![Box::new(TextType)] 25 | } 26 | 27 | fn can_perform_group_eq_op_with(&self) -> Vec> { 28 | vec![Box::new(ArrayType::new(Box::new(TextType)))] 29 | } 30 | 31 | fn can_perform_bang_eq_op_with(&self) -> Vec> { 32 | vec![Box::new(TextType)] 33 | } 34 | 35 | fn can_perform_group_bang_eq_op_with(&self) -> Vec> { 36 | vec![Box::new(ArrayType::new(Box::new(TextType)))] 37 | } 38 | 39 | fn can_perform_gt_op_with(&self) -> Vec> { 40 | vec![Box::new(TextType)] 41 | } 42 | 43 | fn can_perform_group_gt_op_with(&self) -> Vec> { 44 | vec![Box::new(ArrayType::new(Box::new(TextType)))] 45 | } 46 | 47 | fn can_perform_gte_op_with(&self) -> Vec> { 48 | vec![Box::new(TextType)] 49 | } 50 | 51 | fn can_perform_group_gte_op_with(&self) -> Vec> { 52 | vec![Box::new(ArrayType::new(Box::new(TextType)))] 53 | } 54 | 55 | fn can_perform_lt_op_with(&self) -> Vec> { 56 | vec![Box::new(TextType)] 57 | } 58 | 59 | fn can_perform_group_lt_op_with(&self) -> Vec> { 60 | vec![Box::new(ArrayType::new(Box::new(TextType)))] 61 | } 62 | 63 | fn can_perform_lte_op_with(&self) -> Vec> { 64 | vec![Box::new(TextType)] 65 | } 66 | 67 | fn can_perform_group_lte_op_with(&self) -> Vec> { 68 | vec![Box::new(ArrayType::new(Box::new(TextType)))] 69 | } 70 | 71 | fn can_perform_like_op_with(&self) -> Vec> { 72 | vec![Box::new(TextType)] 73 | } 74 | 75 | fn can_perform_glob_op_with(&self) -> Vec> { 76 | vec![Box::new(TextType)] 77 | } 78 | 79 | fn can_perform_regexp_op_with(&self) -> Vec> { 80 | vec![Box::new(TextType)] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/time.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::expression::Expr; 4 | use crate::expression::StringExpr; 5 | use crate::format_checker::is_valid_time_format; 6 | use crate::types::array::ArrayType; 7 | 8 | use super::base::DataType; 9 | 10 | #[derive(Clone)] 11 | pub struct TimeType; 12 | 13 | impl DataType for TimeType { 14 | fn literal(&self) -> String { 15 | "Time".to_string() 16 | } 17 | 18 | fn equals(&self, other: &Box) -> bool { 19 | other.is_any() || other.is_time() || other.is_variant_with(|t| t.is_time()) 20 | } 21 | 22 | fn as_any(&self) -> &dyn Any { 23 | self 24 | } 25 | 26 | fn can_perform_eq_op_with(&self) -> Vec> { 27 | vec![Box::new(TimeType)] 28 | } 29 | 30 | fn can_perform_group_eq_op_with(&self) -> Vec> { 31 | vec![Box::new(ArrayType::new(Box::new(TimeType)))] 32 | } 33 | 34 | fn can_perform_bang_eq_op_with(&self) -> Vec> { 35 | vec![Box::new(TimeType)] 36 | } 37 | 38 | fn can_perform_group_bang_eq_op_with(&self) -> Vec> { 39 | vec![Box::new(ArrayType::new(Box::new(TimeType)))] 40 | } 41 | 42 | fn can_perform_gt_op_with(&self) -> Vec> { 43 | vec![Box::new(TimeType)] 44 | } 45 | 46 | fn can_perform_group_gt_op_with(&self) -> Vec> { 47 | vec![Box::new(ArrayType::new(Box::new(TimeType)))] 48 | } 49 | 50 | fn can_perform_gte_op_with(&self) -> Vec> { 51 | vec![Box::new(TimeType)] 52 | } 53 | 54 | fn can_perform_group_gte_op_with(&self) -> Vec> { 55 | vec![Box::new(ArrayType::new(Box::new(TimeType)))] 56 | } 57 | 58 | fn can_perform_lt_op_with(&self) -> Vec> { 59 | vec![Box::new(TimeType)] 60 | } 61 | 62 | fn can_perform_group_lt_op_with(&self) -> Vec> { 63 | vec![Box::new(ArrayType::new(Box::new(TimeType)))] 64 | } 65 | 66 | fn can_perform_lte_op_with(&self) -> Vec> { 67 | vec![Box::new(TimeType)] 68 | } 69 | 70 | fn can_perform_group_lte_op_with(&self) -> Vec> { 71 | vec![Box::new(ArrayType::new(Box::new(TimeType)))] 72 | } 73 | 74 | fn has_implicit_cast_from(&self, expr: &Box) -> bool { 75 | if let Some(string_expr) = expr.as_any().downcast_ref::() { 76 | return is_valid_time_format(&string_expr.value); 77 | } 78 | false 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/undefined.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::base::DataType; 4 | 5 | #[derive(Clone)] 6 | pub struct UndefType; 7 | 8 | impl DataType for UndefType { 9 | fn literal(&self) -> String { 10 | "Undef".to_string() 11 | } 12 | 13 | fn equals(&self, other: &Box) -> bool { 14 | other.as_any().downcast_ref::().is_some() 15 | } 16 | 17 | fn as_any(&self) -> &dyn Any { 18 | self 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/varargs.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::base::DataType; 4 | 5 | #[derive(Clone)] 6 | pub struct VarargsType { 7 | pub base: Box, 8 | } 9 | 10 | impl VarargsType { 11 | pub fn new(base: Box) -> Self { 12 | VarargsType { base } 13 | } 14 | } 15 | 16 | impl DataType for VarargsType { 17 | fn literal(&self) -> String { 18 | format!("...{}", self.base.literal()) 19 | } 20 | 21 | fn equals(&self, other: &Box) -> bool { 22 | other.is_any() || self.base.equals(other) 23 | } 24 | 25 | fn as_any(&self) -> &dyn Any { 26 | self 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/gitql-ast/src/types/variant.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use super::base::DataType; 4 | 5 | #[derive(Clone)] 6 | pub struct VariantType { 7 | pub variants: Vec>, 8 | } 9 | 10 | impl VariantType { 11 | pub fn new(variants: Vec>) -> Self { 12 | VariantType { variants } 13 | } 14 | } 15 | 16 | impl DataType for VariantType { 17 | fn literal(&self) -> String { 18 | let mut str = String::new(); 19 | let last_position = self.variants.len() - 1; 20 | str += "["; 21 | for (pos, data_type) in self.variants.iter().enumerate() { 22 | str += &data_type.literal(); 23 | if pos != last_position { 24 | str += " | "; 25 | } 26 | } 27 | str += "]"; 28 | str 29 | } 30 | 31 | fn equals(&self, other: &Box) -> bool { 32 | if other.is_any() { 33 | return true; 34 | } 35 | 36 | for variant in self.variants.iter() { 37 | if variant.equals(other) { 38 | return true; 39 | } 40 | } 41 | 42 | false 43 | } 44 | 45 | fn as_any(&self) -> &dyn Any { 46 | self 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/gitql-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gitql-cli" 3 | authors = ["AmrDeveloper"] 4 | version = "0.38.0" 5 | edition = "2021" 6 | description = "GitQL Command line interface (CLI) components" 7 | repository = "https://github.com/amrdeveloper/gql/tree/main/crates/gitql-cli" 8 | license = "MIT" 9 | keywords = ["cli", "gql", "language", "git", "sql"] 10 | categories = ["command-line-utilities"] 11 | 12 | [dependencies] 13 | gitql-core = { path = "../gitql-core", version = "0.15.0" } 14 | gitql-ast = { path = "../gitql-ast", version = "0.34.0" } 15 | gitql-parser = { path = "../gitql-parser", version = "0.37.0" } 16 | comfy-table = { workspace = true } 17 | termcolor = { workspace = true } 18 | serde_json = { workspace = true } 19 | yaml-rust = { workspace = true } 20 | csv = { workspace = true } 21 | linked-hash-map = { workspace = true } 22 | -------------------------------------------------------------------------------- /crates/gitql-cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Amr Hesham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/gitql-cli/README.md: -------------------------------------------------------------------------------- 1 |

GQL - Git Query Language CLI


2 | 3 |

4 | Crates.io 5 | Deps 6 | Release 7 | Docs 8 | GitHub release 9 | GitHub issues 10 | GitHub 11 | GitHub all releases 12 |

13 | 14 | 15 | ### License 16 | ``` 17 | MIT License 18 | 19 | Copyright (c) 2023 Amr Hesham 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | ``` 39 | -------------------------------------------------------------------------------- /crates/gitql-cli/src/colored_stream.rs: -------------------------------------------------------------------------------- 1 | use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; 2 | 3 | pub struct ColoredStream { 4 | stdout: StandardStream, 5 | } 6 | 7 | impl Default for ColoredStream { 8 | fn default() -> Self { 9 | Self { 10 | stdout: StandardStream::stdout(ColorChoice::Always), 11 | } 12 | } 13 | } 14 | 15 | impl ColoredStream { 16 | pub fn set_color(&mut self, color: Option) { 17 | _ = self.stdout.set_color(ColorSpec::new().set_fg(color)); 18 | } 19 | 20 | pub fn reset(&mut self) { 21 | _ = self.stdout.reset(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/gitql-cli/src/diagnostic_reporter.rs: -------------------------------------------------------------------------------- 1 | use gitql_parser::diagnostic::Diagnostic; 2 | 3 | use termcolor::Color; 4 | 5 | use crate::colored_stream::ColoredStream; 6 | 7 | #[derive(Default)] 8 | pub struct DiagnosticReporter { 9 | stdout: ColoredStream, 10 | } 11 | 12 | impl DiagnosticReporter { 13 | pub fn report_diagnostic(&mut self, query: &str, diagnostic: Diagnostic) { 14 | self.stdout.set_color(Some(Color::Red)); 15 | println!("[{}]: {}", diagnostic.label(), diagnostic.message()); 16 | 17 | if let Some(location) = diagnostic.location() { 18 | println!( 19 | " --> Location {}:{}", 20 | location.line_start, location.column_start 21 | ); 22 | } 23 | 24 | if !query.is_empty() { 25 | println!(" |"); 26 | if let Some(location) = diagnostic.location() { 27 | let lines: Vec<&str> = query.split('\n').collect(); 28 | let end = u32::min(location.line_end, lines.len() as u32); 29 | for zero_based_line_number in location.line_start - 1..end { 30 | println!("-> | {}", lines[zero_based_line_number as usize]); 31 | } 32 | 33 | println!(" | "); 34 | let column_s = location.column_start.saturating_sub(1) as usize; 35 | print!(" | {}", &"-".repeat(column_s)); 36 | 37 | let diagnostic_length = 38 | u32::max(1, location.column_end.saturating_sub(location.column_start)) as usize; 39 | 40 | self.stdout.set_color(Some(Color::Yellow)); 41 | println!("{}", &"^".repeat(diagnostic_length)); 42 | 43 | self.stdout.set_color(Some(Color::Red)); 44 | } 45 | println!(" |"); 46 | } 47 | 48 | self.stdout.set_color(Some(Color::Cyan)); 49 | for help in diagnostic.helps() { 50 | println!(" = Help: {}", help); 51 | } 52 | 53 | self.stdout.set_color(Some(Color::Yellow)); 54 | for note in diagnostic.notes() { 55 | println!(" = Note: {}", note); 56 | } 57 | 58 | self.stdout.set_color(Some(Color::Blue)); 59 | if let Some(docs) = diagnostic.docs() { 60 | println!(" = Docs: {}", docs); 61 | } 62 | 63 | self.stdout.reset(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/gitql-cli/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod arguments; 2 | pub mod colored_stream; 3 | pub mod diagnostic_reporter; 4 | pub mod printer; 5 | -------------------------------------------------------------------------------- /crates/gitql-cli/src/printer/csv_printer.rs: -------------------------------------------------------------------------------- 1 | use std::io::stdout; 2 | use std::io::Write; 3 | 4 | use csv::Writer; 5 | use gitql_core::object::GitQLObject; 6 | 7 | use super::BaseOutputPrinter; 8 | 9 | pub struct CSVPrinter; 10 | 11 | impl BaseOutputPrinter for CSVPrinter { 12 | fn print(&self, object: &mut GitQLObject) { 13 | let mut writer = Writer::from_writer(vec![]); 14 | let _ = writer.write_record(object.titles.clone()); 15 | let row_len = object.titles.len(); 16 | if let Some(group) = object.groups.first() { 17 | for row in &group.rows { 18 | let mut values_row: Vec = Vec::with_capacity(row_len); 19 | for value in &row.values { 20 | values_row.push(value.literal()); 21 | } 22 | let _ = writer.write_record(values_row); 23 | } 24 | } 25 | 26 | if let Ok(writer_content) = writer.into_inner() { 27 | if let Ok(content) = String::from_utf8(writer_content) { 28 | if let Err(error) = writeln!(stdout(), "{}", content) { 29 | eprintln!("{}", error); 30 | std::process::exit(1); 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/gitql-cli/src/printer/json_printer.rs: -------------------------------------------------------------------------------- 1 | use std::io::stdout; 2 | use std::io::Write; 3 | 4 | use gitql_core::object::GitQLObject; 5 | 6 | use super::BaseOutputPrinter; 7 | 8 | pub struct JSONPrinter; 9 | 10 | impl BaseOutputPrinter for JSONPrinter { 11 | fn print(&self, object: &mut GitQLObject) { 12 | let mut elements: Vec = vec![]; 13 | 14 | if let Some(group) = object.groups.first() { 15 | let titles = &object.titles; 16 | for row in &group.rows { 17 | let mut object = serde_json::Map::new(); 18 | for (i, value) in row.values.iter().enumerate() { 19 | object.insert( 20 | titles[i].to_string(), 21 | serde_json::Value::String(value.literal()), 22 | ); 23 | } 24 | elements.push(serde_json::Value::Object(object)); 25 | } 26 | } 27 | 28 | if let Ok(json_str) = serde_json::to_string(&serde_json::Value::Array(elements)) { 29 | if let Err(error) = writeln!(stdout(), "{}", json_str) { 30 | eprintln!("{}", error); 31 | std::process::exit(1); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/gitql-cli/src/printer/mod.rs: -------------------------------------------------------------------------------- 1 | use gitql_core::object::GitQLObject; 2 | 3 | /// Represent the different type of available formats 4 | #[derive(Debug, PartialEq)] 5 | pub enum OutputFormatKind { 6 | /// Render the output as table 7 | Table, 8 | /// Print the output in JSON format 9 | JSON, 10 | /// Print the output in CSV format 11 | CSV, 12 | /// Print the output in YAML format 13 | YAML, 14 | } 15 | 16 | pub trait BaseOutputPrinter { 17 | fn print(&self, object: &mut GitQLObject); 18 | } 19 | 20 | mod csv_printer; 21 | pub use csv_printer::CSVPrinter; 22 | 23 | mod json_printer; 24 | pub use json_printer::JSONPrinter; 25 | 26 | mod table_printer; 27 | pub use table_printer::TablePrinter; 28 | 29 | mod yaml_printer; 30 | pub use yaml_printer::YAMLPrinter; 31 | -------------------------------------------------------------------------------- /crates/gitql-cli/src/printer/yaml_printer.rs: -------------------------------------------------------------------------------- 1 | use std::io::stdout; 2 | use std::io::Write; 3 | 4 | use gitql_core::object::GitQLObject; 5 | use linked_hash_map::LinkedHashMap; 6 | use yaml_rust::Yaml; 7 | use yaml_rust::YamlEmitter; 8 | 9 | use super::BaseOutputPrinter; 10 | 11 | pub struct YAMLPrinter; 12 | 13 | impl BaseOutputPrinter for YAMLPrinter { 14 | fn print(&self, object: &mut GitQLObject) { 15 | let mut out_str = String::new(); 16 | let mut emitter = YamlEmitter::new(&mut out_str); 17 | 18 | let columns_titles = &object.titles; 19 | 20 | let main_group = object.groups.first().unwrap(); 21 | let mut rows_rows: Vec = Vec::with_capacity(main_group.rows.len()); 22 | 23 | for row in main_group.rows.clone() { 24 | let mut vec: LinkedHashMap = LinkedHashMap::new(); 25 | for (column_index, column_value) in row.values.iter().enumerate() { 26 | vec.insert( 27 | Yaml::String(columns_titles[column_index].to_string()), 28 | Yaml::String(column_value.to_string()), 29 | ); 30 | } 31 | let row_yaml = Yaml::Hash(vec); 32 | rows_rows.push(row_yaml); 33 | } 34 | 35 | if let Err(error) = emitter.dump(&Yaml::Array(rows_rows)) { 36 | eprintln!("{}", error); 37 | std::process::exit(1); 38 | } 39 | 40 | if let Err(error) = writeln!(stdout(), "{}", out_str) { 41 | eprintln!("{}", error); 42 | std::process::exit(1); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/gitql-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gitql-core" 3 | authors = ["AmrDeveloper"] 4 | version = "0.15.0" 5 | edition = "2021" 6 | description = "GitQL Core components" 7 | repository = "https://github.com/amrdeveloper/gql/tree/main/crates/gitql-cli" 8 | license = "MIT" 9 | keywords = ["cli", "gql", "language", "git", "sql"] 10 | categories = ["command-line-utilities"] 11 | 12 | [dependencies] 13 | gitql-ast = { path = "../gitql-ast", version = "0.34.0" } 14 | chrono = { workspace = true } 15 | dyn-clone = { workspace = true } 16 | indexmap = { workspace = true } 17 | regex = { workspace = true } 18 | -------------------------------------------------------------------------------- /crates/gitql-core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Amr Hesham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/gitql-core/README.md: -------------------------------------------------------------------------------- 1 |

GQL - Git Query Language Core components


2 | 3 |

4 | Crates.io 5 | Deps 6 | Release 7 | Docs 8 | GitHub release 9 | GitHub issues 10 | GitHub 11 | GitHub all releases 12 |

13 | 14 | 15 | ### License 16 | ``` 17 | MIT License 18 | 19 | Copyright (c) 2023 Amr Hesham 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | ``` 39 | -------------------------------------------------------------------------------- /crates/gitql-core/src/combinations_generator.rs: -------------------------------------------------------------------------------- 1 | /// Return a list of all non empty and unique combinations 2 | pub fn generate_list_of_all_combinations(n: usize) -> Vec> { 3 | let mut result = Vec::with_capacity((2 << n) - 1); 4 | let mut current = Vec::with_capacity(n); 5 | generate_indices_combination(n, 0, &mut current, &mut result); 6 | result 7 | } 8 | 9 | fn generate_indices_combination( 10 | n: usize, 11 | start: usize, 12 | current: &mut Vec, 13 | result: &mut Vec>, 14 | ) { 15 | if !current.is_empty() { 16 | result.push(current.clone()); 17 | } 18 | 19 | for i in start..n { 20 | current.push(i); 21 | generate_indices_combination(n, i + 1, current, result); 22 | current.pop(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/gitql-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod combinations_generator; 2 | pub mod environment; 3 | pub mod object; 4 | pub mod schema; 5 | pub mod signature; 6 | pub mod types_table; 7 | pub mod values; 8 | 9 | // Export IndexMap type 10 | pub use indexmap; 11 | -------------------------------------------------------------------------------- /crates/gitql-core/src/object.rs: -------------------------------------------------------------------------------- 1 | use super::values::Value; 2 | 3 | /// In memory representation of the list of [`Value`] in one Row 4 | #[derive(Clone, Default)] 5 | pub struct Row { 6 | pub values: Vec>, 7 | } 8 | 9 | /// In memory representation of the Rows of one [`Group`] 10 | #[derive(Clone, Default)] 11 | pub struct Group { 12 | pub rows: Vec, 13 | } 14 | 15 | impl Group { 16 | /// Returns true of this group has no rows 17 | pub fn is_empty(&self) -> bool { 18 | self.rows.is_empty() 19 | } 20 | 21 | /// Returns the number of rows in this group 22 | pub fn len(&self) -> usize { 23 | self.rows.len() 24 | } 25 | } 26 | 27 | /// In memory representation of the GitQL Object which has titles and groups 28 | #[derive(Default)] 29 | pub struct GitQLObject { 30 | pub titles: Vec, 31 | pub groups: Vec, 32 | } 33 | 34 | impl GitQLObject { 35 | /// Flat the list of current groups into one main group 36 | pub fn flat(&mut self) { 37 | let mut rows: Vec = vec![]; 38 | for group in &mut self.groups { 39 | rows.append(&mut group.rows); 40 | } 41 | 42 | self.groups.clear(); 43 | self.groups.push(Group { rows }) 44 | } 45 | 46 | /// Returns true of there is no groups 47 | pub fn is_empty(&self) -> bool { 48 | self.groups.is_empty() 49 | } 50 | 51 | /// Returns the number of groups in this Object 52 | pub fn len(&self) -> usize { 53 | self.groups.len() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crates/gitql-core/src/schema.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use gitql_ast::types::DataType; 4 | 5 | /// A Representation of the Data Schema that constructed the following 6 | /// 7 | /// [`tables_fields_names`] is a map of tables and columns names 8 | /// 9 | /// # Examples 10 | /// 11 | /// ``` 12 | /// 13 | /// pub static ref TABLES_FIELDS_NAMES: HashMap<&'static str, Vec<&'static str>> = { 14 | /// let mut map = HashMap::new(); 15 | /// map.insert("refs", vec!["name", "full_name", "type", "repo"]); 16 | /// } 17 | /// 18 | /// ``` 19 | /// 20 | /// [`tables_fields_types`] is a map of each column name in general with the expected data type 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// pub static ref TABLES_FIELDS_TYPES: HashMap<&'static str, Box> = { 26 | /// let mut map = HashMap::new(); 27 | /// map.insert("commit_id", Box::new(TextType)); 28 | /// } 29 | /// ``` 30 | /// 31 | pub struct Schema { 32 | pub tables_fields_names: HashMap<&'static str, Vec<&'static str>>, 33 | pub tables_fields_types: HashMap<&'static str, Box>, 34 | } 35 | -------------------------------------------------------------------------------- /crates/gitql-core/src/signature.rs: -------------------------------------------------------------------------------- 1 | use super::values::Value; 2 | 3 | use gitql_ast::types::DataType; 4 | 5 | /// Standard function accept array of values and return single [`Value`] 6 | pub type StandardFunction = fn(&[Box]) -> Box; 7 | 8 | /// Aggregation function accept a selected row values for each row in group and return single [`Value`] 9 | /// 10 | /// [`Vec>`] represent the selected values from each row in group 11 | /// 12 | /// For Example if we have three rows in group and select name and email from each one 13 | /// 14 | /// [[name, email], [name, email], [name, email]] 15 | /// 16 | /// This implementation allow aggregation function to accept more than one parameter, 17 | /// and also accept any Expression not only field name 18 | /// 19 | pub type AggregationFunction = fn(&[Vec>]) -> Box; 20 | 21 | /// Window function a selected row values for each row in a specific frame and return single [`Value`] 22 | /// 23 | /// [`Vec>`] represent the selected values from each row in frame of rows 24 | /// 25 | /// For Example if we have three rows in frame of row and select name and email from each one 26 | /// 27 | /// [[name, email], [name, email], [name, email]] 28 | /// 29 | /// This implementation allow Window` function to accept more than one parameter, 30 | /// and also accept any Expression not only field name 31 | /// 32 | pub type WindowFunction = fn(&[Vec>]) -> Vec>; 33 | 34 | /// Signature struct is a representation of function type 35 | /// 36 | /// Function type in GitQL used to track parameters and return type for now 37 | /// but later can track parameter names to allow pass parameter by name and improve error messages 38 | /// 39 | /// GitQL Function Signature has some rules to follow 40 | /// 41 | /// Rules: 42 | /// - Parameters must contains 0 or 1 [`VarargsType`] parameter type only. 43 | /// - [`VarargsType`] must be the last parameter. 44 | /// - You can add 0 or more [`DataType::Optional`] parameters. 45 | /// - [`OptionalType`] parameters must be at the end but also before [`VarargsType`] if exists. 46 | /// 47 | /// The return type can be a static [`DataType`] such as Int, Float or Dynamic 48 | /// so you can return a dynamic type depending on parameters. 49 | #[derive(Clone)] 50 | pub struct Signature { 51 | pub parameters: Vec>, 52 | pub return_type: Box, 53 | } 54 | 55 | impl Signature { 56 | /// Create Instance of [`Signature`] with parameters and return type 57 | pub fn new(parameters: Vec>, return_type: Box) -> Self { 58 | Signature { 59 | parameters, 60 | return_type, 61 | } 62 | } 63 | 64 | /// Create Instance of [`Signature`] with return type and zero parameters 65 | pub fn with_return(return_type: Box) -> Self { 66 | Signature { 67 | parameters: Vec::default(), 68 | return_type, 69 | } 70 | } 71 | 72 | /// Add list of parameters to the [`Signature`] 73 | pub fn add_parameters(mut self, mut parameters: Vec>) -> Self { 74 | self.parameters.append(&mut parameters); 75 | self 76 | } 77 | 78 | /// Add parameter to the [`Signature`] 79 | pub fn add_parameter(mut self, parameter: Box) -> Self { 80 | self.parameters.push(parameter); 81 | self 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/gitql-core/src/types_table.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use gitql_ast::types::boolean::BoolType; 4 | use gitql_ast::types::date::DateType; 5 | use gitql_ast::types::datetime::DateTimeType; 6 | use gitql_ast::types::float::FloatType; 7 | use gitql_ast::types::integer::IntType; 8 | use gitql_ast::types::text::TextType; 9 | use gitql_ast::types::time::TimeType; 10 | use gitql_ast::types::DataType; 11 | 12 | /// Map of Types and Names to be used in type parser 13 | pub struct TypesTable { 14 | /// Collection of type names mapped to actual types 15 | types_map: HashMap<&'static str, Box>, 16 | } 17 | 18 | impl Default for TypesTable { 19 | fn default() -> Self { 20 | Self::new() 21 | } 22 | } 23 | 24 | impl TypesTable { 25 | /// Create new Instance of [`TypesTable`] with SQL primitives types registered 26 | pub fn new() -> Self { 27 | let mut types_table = TypesTable { 28 | types_map: HashMap::default(), 29 | }; 30 | register_primitives_types(&mut types_table.types_map); 31 | types_table 32 | } 33 | 34 | /// Create new Instance of [`TypesTable`] with empty map 35 | pub fn empty() -> Self { 36 | TypesTable { 37 | types_map: HashMap::default(), 38 | } 39 | } 40 | 41 | /// Register DataType to a new name and return optional if it success or not 42 | pub fn register( 43 | &mut self, 44 | name: &'static str, 45 | data_type: Box, 46 | ) -> Option> { 47 | self.types_map.insert(name, data_type) 48 | } 49 | 50 | /// Lookup at the types map by name and return DataType if registered or None if not found 51 | pub fn lookup(&self, name: &str) -> Option> { 52 | self.types_map.get(&name).cloned() 53 | } 54 | 55 | /// Return Reference to the current type map 56 | pub fn types_map(&self) -> &HashMap<&'static str, Box> { 57 | &self.types_map 58 | } 59 | 60 | /// Returns true if the map contains no elements. 61 | pub fn is_empty(&self) -> bool { 62 | self.types_map.is_empty() 63 | } 64 | 65 | /// Return the length of types map 66 | pub fn len(&self) -> usize { 67 | self.types_map.len() 68 | } 69 | } 70 | 71 | /// Register the common predefined Types in SQL with their common aliases 72 | fn register_primitives_types(types_map: &mut HashMap<&'static str, Box>) { 73 | // SQL Data Types 74 | types_map.insert("integer", Box::new(IntType)); 75 | types_map.insert("real", Box::new(FloatType)); 76 | types_map.insert("boolean", Box::new(BoolType)); 77 | types_map.insert("text", Box::new(TextType)); 78 | types_map.insert("date", Box::new(DateType)); 79 | types_map.insert("time", Box::new(TimeType)); 80 | types_map.insert("datetime", Box::new(DateTimeType)); 81 | 82 | // SQL Type Aliases 83 | types_map.insert("int", Box::new(IntType)); 84 | types_map.insert("float", Box::new(FloatType)); 85 | types_map.insert("bool", Box::new(BoolType)); 86 | } 87 | -------------------------------------------------------------------------------- /crates/gitql-core/src/values/composite.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cmp::Ordering; 3 | use std::collections::HashMap; 4 | 5 | use gitql_ast::types::composite::CompositeType; 6 | use gitql_ast::types::DataType; 7 | 8 | use indexmap::IndexMap; 9 | 10 | use super::base::Value; 11 | 12 | #[derive(Clone)] 13 | pub struct CompositeValue { 14 | pub name: String, 15 | pub members: IndexMap>, 16 | } 17 | 18 | impl CompositeValue { 19 | pub fn new(name: String, members: IndexMap>) -> Self { 20 | CompositeValue { name, members } 21 | } 22 | 23 | pub fn empty(name: String) -> Self { 24 | CompositeValue { 25 | name, 26 | members: IndexMap::default(), 27 | } 28 | } 29 | 30 | pub fn add_member(mut self, name: String, value: Box) -> Self { 31 | self.members.insert(name, value); 32 | self 33 | } 34 | } 35 | 36 | impl Value for CompositeValue { 37 | fn literal(&self) -> String { 38 | let mut str = String::new(); 39 | let last_position = self.members.len() - 1; 40 | str += "("; 41 | for (pos, member) in self.members.iter().enumerate() { 42 | str += &member.1.literal(); 43 | if pos != last_position { 44 | str += ", "; 45 | } 46 | } 47 | str += ")"; 48 | str 49 | } 50 | 51 | fn equals(&self, other: &Box) -> bool { 52 | if let Some(other_composite) = other.as_any().downcast_ref::() { 53 | return self.name.eq(&other_composite.name) 54 | && self.members.eq(&other_composite.members); 55 | } 56 | false 57 | } 58 | 59 | fn compare(&self, _other: &Box) -> Option { 60 | None 61 | } 62 | 63 | fn data_type(&self) -> Box { 64 | let name = self.name.to_string(); 65 | let mut members: HashMap> = HashMap::new(); 66 | for member in self.members.iter() { 67 | members.insert(member.0.to_string(), member.1.data_type().clone()); 68 | } 69 | Box::new(CompositeType::new(name, members)) 70 | } 71 | 72 | fn as_any(&self) -> &dyn Any { 73 | self 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/gitql-core/src/values/converters.rs: -------------------------------------------------------------------------------- 1 | use crate::values::boolean::BoolValue; 2 | use crate::values::date::DateValue; 3 | use crate::values::datetime::DateTimeValue; 4 | use crate::values::null::NullValue; 5 | use crate::values::time::TimeValue; 6 | use crate::values::Value; 7 | 8 | pub fn string_literal_to_time(literal: &str) -> Box { 9 | Box::new(TimeValue { 10 | value: literal.to_string(), 11 | }) 12 | } 13 | 14 | pub fn string_literal_to_date(literal: &str) -> Box { 15 | let date_time = chrono::NaiveDate::parse_from_str(literal, "%Y-%m-%d").ok(); 16 | let timestamp = if let Some(date) = date_time { 17 | let zero_time = chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap(); 18 | date.and_time(zero_time).and_utc().timestamp() 19 | } else { 20 | 0 21 | }; 22 | 23 | Box::new(DateValue { timestamp }) 24 | } 25 | 26 | pub fn string_literal_to_date_time(literal: &str) -> Box { 27 | let date_time_format = if literal.contains('.') { 28 | "%Y-%m-%d %H:%M:%S%.3f" 29 | } else { 30 | "%Y-%m-%d %H:%M:%S" 31 | }; 32 | 33 | let date_time = chrono::NaiveDateTime::parse_from_str(literal, date_time_format); 34 | if date_time.is_err() { 35 | return Box::new(DateTimeValue { value: 0 }); 36 | } 37 | 38 | let timestamp = date_time.ok().unwrap().and_utc().timestamp(); 39 | Box::new(DateTimeValue { value: timestamp }) 40 | } 41 | 42 | pub fn string_literal_to_boolean(literal: &str) -> Box { 43 | match literal { 44 | // True values literal 45 | "t" => Box::new(BoolValue::new_true()), 46 | "true" => Box::new(BoolValue::new_true()), 47 | "y" => Box::new(BoolValue::new_true()), 48 | "yes" => Box::new(BoolValue::new_true()), 49 | "1" => Box::new(BoolValue::new_true()), 50 | // False values literal 51 | "f" => Box::new(BoolValue::new_false()), 52 | "false" => Box::new(BoolValue::new_false()), 53 | "n" => Box::new(BoolValue::new_false()), 54 | "no" => Box::new(BoolValue::new_false()), 55 | "0" => Box::new(BoolValue::new_false()), 56 | // Invalid value, must be unreachable 57 | _ => Box::new(NullValue), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/gitql-core/src/values/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod array; 2 | pub mod boolean; 3 | pub mod composite; 4 | pub mod converters; 5 | pub mod date; 6 | pub mod datetime; 7 | pub mod float; 8 | pub mod integer; 9 | pub mod interval; 10 | pub mod null; 11 | pub mod range; 12 | pub mod text; 13 | pub mod time; 14 | 15 | mod base; 16 | pub use base::Value; 17 | -------------------------------------------------------------------------------- /crates/gitql-core/src/values/null.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cmp::Ordering; 3 | 4 | use gitql_ast::types::null::NullType; 5 | use gitql_ast::types::DataType; 6 | 7 | use super::base::Value; 8 | 9 | #[derive(Clone)] 10 | pub struct NullValue; 11 | 12 | impl Value for NullValue { 13 | fn literal(&self) -> String { 14 | "Null".to_string() 15 | } 16 | 17 | fn equals(&self, other: &Box) -> bool { 18 | other.as_any().downcast_ref::().is_some() 19 | } 20 | 21 | fn compare(&self, _other: &Box) -> Option { 22 | None 23 | } 24 | 25 | fn data_type(&self) -> Box { 26 | Box::new(NullType) 27 | } 28 | 29 | fn as_any(&self) -> &dyn Any { 30 | self 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/gitql-core/src/values/range.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cmp::Ordering; 3 | 4 | use gitql_ast::types::range::RangeType; 5 | use gitql_ast::types::DataType; 6 | 7 | use super::base::Value; 8 | use super::boolean::BoolValue; 9 | 10 | #[derive(Clone)] 11 | pub struct RangeValue { 12 | pub start: Box, 13 | pub end: Box, 14 | pub base_type: Box, 15 | } 16 | 17 | impl RangeValue { 18 | pub fn new(start: Box, end: Box, base_type: Box) -> Self { 19 | RangeValue { 20 | start, 21 | end, 22 | base_type, 23 | } 24 | } 25 | } 26 | 27 | impl Value for RangeValue { 28 | fn literal(&self) -> String { 29 | format!("{}..{}", self.start.literal(), self.end.literal()) 30 | } 31 | 32 | fn equals(&self, other: &Box) -> bool { 33 | if let Some(other_range) = other.as_any().downcast_ref::() { 34 | return self.base_type.equals(&other_range.base_type) 35 | && self.start.equals(&other_range.start) 36 | && self.end.equals(&other_range.end); 37 | } 38 | false 39 | } 40 | 41 | fn compare(&self, _other: &Box) -> Option { 42 | None 43 | } 44 | 45 | fn data_type(&self) -> Box { 46 | Box::new(RangeType { 47 | base: self.base_type.clone(), 48 | }) 49 | } 50 | 51 | fn as_any(&self) -> &dyn Any { 52 | self 53 | } 54 | 55 | fn logical_or_op(&self, other: &Box) -> Result, String> { 56 | if let Some(other_range) = other.as_any().downcast_ref::() { 57 | if !self.equals(other) { 58 | return Err("Overlap operator expect both Ranges to have same type".to_string()); 59 | } 60 | 61 | let max_start = if self.start.compare(&other_range.start).unwrap().is_ge() { 62 | &self.start 63 | } else { 64 | &other_range.start 65 | }; 66 | 67 | let max_end = if self.end.compare(&other_range.end).unwrap().is_lt() { 68 | &self.end 69 | } else { 70 | &other_range.end 71 | }; 72 | 73 | let is_overlap = max_end.compare(max_start).unwrap().is_ge(); 74 | return Ok(Box::new(BoolValue { value: is_overlap })); 75 | } 76 | Err("Unexpected type to perform `Range Overlap &&` with".to_string()) 77 | } 78 | 79 | fn contains_op(&self, other: &Box) -> Result, String> { 80 | if let Some(other_range) = other.as_any().downcast_ref::() { 81 | let is_in_range = other_range.start.compare(&self.start).unwrap().is_ge() 82 | && other_range.end.compare(&self.end).unwrap().is_le(); 83 | return Ok(Box::new(BoolValue { value: is_in_range })); 84 | } 85 | 86 | if self.base_type.equals(&other.data_type()) { 87 | let is_in_range = other.compare(&self.start).unwrap().is_ge() 88 | && other.compare(&self.end).unwrap().is_le(); 89 | return Ok(Box::new(BoolValue { value: is_in_range })); 90 | } 91 | 92 | Err("Unexpected type to perform `Range contains @>` with".to_string()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /crates/gitql-engine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gitql-engine" 3 | authors = ["AmrDeveloper"] 4 | version = "0.38.0" 5 | edition = "2021" 6 | description = "GitQL Engine" 7 | repository = "https://github.com/amrdeveloper/gql/tree/main/crates/gitql-engine" 8 | license = "MIT" 9 | keywords = ["cli", "gql", "language", "git", "sql"] 10 | categories = ["command-line-utilities"] 11 | 12 | [dependencies] 13 | gitql-core = { path = "../gitql-core", version = "0.15.0" } 14 | gitql-ast = { path = "../gitql-ast", version = "0.34.0" } 15 | chrono = { workspace = true } 16 | -------------------------------------------------------------------------------- /crates/gitql-engine/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Amr Hesham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/gitql-engine/README.md: -------------------------------------------------------------------------------- 1 |

GQL - Git Query Language Execution Engine


2 | 3 |

4 | Crates.io 5 | Deps 6 | Release 7 | Docs 8 | GitHub release 9 | GitHub issues 10 | GitHub 11 | GitHub all releases 12 |

13 | 14 | 15 | ### License 16 | ``` 17 | MIT License 18 | 19 | Copyright (c) 2023 Amr Hesham 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | ``` 39 | -------------------------------------------------------------------------------- /crates/gitql-engine/src/data_provider.rs: -------------------------------------------------------------------------------- 1 | use gitql_core::object::Row; 2 | 3 | /// DataProvider is a component that used to provide and map the data to the GitQL Engine 4 | /// 5 | /// User should implement [`DataProvider`] trait for each data format for example files, logs, api 6 | pub trait DataProvider { 7 | fn provide(&self, table: &str, selected_columns: &[String]) -> Result, String>; 8 | } 9 | -------------------------------------------------------------------------------- /crates/gitql-engine/src/engine_distinct.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::hash::DefaultHasher; 3 | use std::hash::Hash; 4 | use std::hash::Hasher; 5 | 6 | use gitql_ast::statement::Distinct; 7 | use gitql_core::object::GitQLObject; 8 | use gitql_core::object::Group; 9 | use gitql_core::object::Row; 10 | 11 | /// Apply the distinct operator depending on the type of distinct 12 | pub(crate) fn apply_distinct_operator( 13 | distinct: &Distinct, 14 | object: &mut GitQLObject, 15 | hidden_selections: &[String], 16 | ) { 17 | if object.is_empty() { 18 | return; 19 | } 20 | 21 | match distinct { 22 | Distinct::DistinctAll => apply_distinct_all_operation(object, hidden_selections), 23 | Distinct::DistinctOn(fields) => apply_distinct_on_operation(object, fields), 24 | _ => {} 25 | } 26 | } 27 | 28 | /// Apply Distinct all operator that depend on all selected fields in the object 29 | fn apply_distinct_all_operation(object: &mut GitQLObject, hidden_selections: &[String]) { 30 | let titles: Vec<&String> = object 31 | .titles 32 | .iter() 33 | .filter(|s| !hidden_selections.contains(s)) 34 | .collect(); 35 | 36 | let titles_count = titles.len(); 37 | let hidden_selection_count = hidden_selections.len(); 38 | 39 | let objects = &object.groups[0].rows; 40 | let mut new_objects = Group { rows: vec![] }; 41 | let mut values_set: HashSet = HashSet::new(); 42 | 43 | for object in objects { 44 | // Build row of the selected only values 45 | let mut row_values: Vec = Vec::with_capacity(titles_count); 46 | for i in 0..titles.len() { 47 | if let Some(value) = object.values.get(i + hidden_selection_count) { 48 | row_values.push(value.literal()); 49 | } 50 | } 51 | 52 | // Compute the hash for row of values 53 | let mut hasher = DefaultHasher::new(); 54 | row_values.hash(&mut hasher); 55 | let values_hash = hasher.finish(); 56 | 57 | // If this hash is unique, insert the row 58 | if values_set.insert(values_hash) { 59 | new_objects.rows.push(Row { 60 | values: object.values.clone(), 61 | }); 62 | } 63 | } 64 | 65 | // If number of total rows is changed, update the main group rows 66 | if objects.len() != new_objects.len() { 67 | object.groups[0].rows.clear(); 68 | object.groups[0].rows.append(&mut new_objects.rows); 69 | } 70 | } 71 | 72 | /// Apply Distinct on one or more valid fields from the object 73 | fn apply_distinct_on_operation(object: &mut GitQLObject, distinct_fields: &[String]) { 74 | let objects = &object.groups[0].rows; 75 | let mut new_objects: Group = Group { rows: vec![] }; 76 | let mut values_set: HashSet = HashSet::new(); 77 | let titles = &object.titles; 78 | 79 | for object in objects { 80 | // Build row of the selected only values 81 | let mut row_values: Vec = Vec::with_capacity(distinct_fields.len()); 82 | for field in distinct_fields { 83 | if let Some(index) = titles.iter().position(|r| r.eq(field)) { 84 | row_values.push(object.values.get(index).unwrap().literal()); 85 | } 86 | } 87 | 88 | // Compute the hash for row of values 89 | let mut hasher = DefaultHasher::new(); 90 | row_values.hash(&mut hasher); 91 | 92 | // If this hash is unique, insert the row 93 | if values_set.insert(hasher.finish()) { 94 | new_objects.rows.push(Row { 95 | values: object.values.clone(), 96 | }); 97 | } 98 | } 99 | 100 | // If number of total rows is changed, update the main group rows 101 | if objects.len() != new_objects.len() { 102 | object.groups[0].rows.clear(); 103 | object.groups[0].rows.append(&mut new_objects.rows); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /crates/gitql-engine/src/engine_filter.rs: -------------------------------------------------------------------------------- 1 | use gitql_ast::expression::Expr; 2 | use gitql_core::environment::Environment; 3 | use gitql_core::object::Row; 4 | use gitql_core::values::boolean::BoolValue; 5 | 6 | use crate::engine_evaluator::evaluate_expression; 7 | 8 | #[inline(always)] 9 | #[allow(clippy::borrowed_box)] 10 | pub(crate) fn apply_filter_operation( 11 | env: &mut Environment, 12 | condition: &Box, 13 | titles: &[String], 14 | rows: &mut Vec, 15 | ) -> Result<(), String> { 16 | let mut positions_to_delete = vec![]; 17 | for (index, row) in rows.iter().enumerate() { 18 | let expression = evaluate_expression(env, condition, titles, &row.values)?; 19 | if let Some(bool_value) = expression.as_any().downcast_ref::() { 20 | if !bool_value.value { 21 | positions_to_delete.push(index); 22 | } 23 | } 24 | } 25 | 26 | for position in positions_to_delete.iter().rev() { 27 | rows.remove(*position); 28 | } 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /crates/gitql-engine/src/engine_group.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry::Vacant; 2 | use std::collections::HashMap; 3 | use std::hash::DefaultHasher; 4 | use std::hash::Hash; 5 | use std::hash::Hasher; 6 | 7 | use gitql_ast::statement::GroupByStatement; 8 | use gitql_core::combinations_generator::generate_list_of_all_combinations; 9 | use gitql_core::environment::Environment; 10 | use gitql_core::object::GitQLObject; 11 | use gitql_core::object::Group; 12 | 13 | use crate::engine_evaluator::evaluate_expression; 14 | 15 | pub(crate) fn execute_group_by_statement( 16 | env: &mut Environment, 17 | statement: &GroupByStatement, 18 | gitql_object: &mut GitQLObject, 19 | ) -> Result<(), String> { 20 | if gitql_object.is_empty() { 21 | return Ok(()); 22 | } 23 | 24 | let main_group = gitql_object.groups.remove(0); 25 | if main_group.is_empty() { 26 | return Ok(()); 27 | } 28 | 29 | // Mapping each unique value to it group index 30 | let mut groups_map: HashMap = HashMap::new(); 31 | 32 | // Track current group index 33 | let mut next_group_index = 0; 34 | let values_count = statement.values.len(); 35 | 36 | let is_roll_up_enabled = statement.has_with_roll_up; 37 | let indexes_combinations = if is_roll_up_enabled { 38 | generate_list_of_all_combinations(values_count) 39 | } else { 40 | vec![(0..values_count).collect()] 41 | }; 42 | 43 | // For each row should check the group by values combinations to build multi groups 44 | for row in main_group.rows.iter() { 45 | // Create all combination of values for each row 46 | for indexes in indexes_combinations.iter() { 47 | let mut row_values: Vec = Vec::with_capacity(indexes.len()); 48 | for index in indexes { 49 | let value = evaluate_expression( 50 | env, 51 | &statement.values[*index], 52 | &gitql_object.titles, 53 | &row.values, 54 | )?; 55 | row_values.push(value.literal()); 56 | } 57 | 58 | // Compute the hash for row of values 59 | let mut hasher = DefaultHasher::new(); 60 | row_values.hash(&mut hasher); 61 | let values_hash = hasher.finish(); 62 | 63 | // Push a new group for this unique value and update the next index 64 | if let Vacant(e) = groups_map.entry(values_hash) { 65 | e.insert(next_group_index); 66 | next_group_index += 1; 67 | gitql_object.groups.push(Group { 68 | rows: vec![row.clone()], 69 | }); 70 | continue; 71 | } 72 | 73 | // If there is an existing group for this value, append current object to it 74 | let index = *groups_map.get(&values_hash).unwrap(); 75 | let target_group = &mut gitql_object.groups[index]; 76 | target_group.rows.push(row.clone()); 77 | } 78 | } 79 | 80 | // If the group by elements is one and ROLLUP is enabled 81 | // For example: SELECT ... FROM GROUP BY X WITH ROLLUP 82 | // Should append the the main group at the end 83 | if is_roll_up_enabled && indexes_combinations.len() == 1 && indexes_combinations[0].len() == 1 { 84 | gitql_object.groups.push(main_group); 85 | } 86 | 87 | Ok(()) 88 | } 89 | -------------------------------------------------------------------------------- /crates/gitql-engine/src/engine_ordering.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::collections::HashMap; 3 | 4 | use gitql_ast::statement::NullsOrderPolicy; 5 | use gitql_ast::statement::OrderByStatement; 6 | use gitql_ast::statement::SortingOrder; 7 | use gitql_core::environment::Environment; 8 | use gitql_core::object::GitQLObject; 9 | use gitql_core::object::Group; 10 | use gitql_core::values::null::NullValue; 11 | use gitql_core::values::Value; 12 | 13 | use crate::engine_evaluator::evaluate_expression; 14 | 15 | pub(crate) fn execute_order_by_statement( 16 | env: &mut Environment, 17 | statement: &OrderByStatement, 18 | gitql_object: &mut GitQLObject, 19 | group_index: usize, 20 | ) -> Result<(), String> { 21 | if gitql_object.is_empty() || group_index >= gitql_object.len() { 22 | return Ok(()); 23 | } 24 | 25 | let main_group: &mut Group = &mut gitql_object.groups[group_index]; 26 | if main_group.is_empty() { 27 | return Ok(()); 28 | } 29 | 30 | let rows_len = main_group.rows.len(); 31 | let arguments_len = statement.arguments.len(); 32 | let main_group_rows = &main_group.rows; 33 | let titles = &gitql_object.titles; 34 | 35 | let mut eval_map: HashMap>> = HashMap::with_capacity(rows_len); 36 | 37 | for row in main_group_rows.iter() { 38 | let row_addr = row.values.as_ptr() as usize; 39 | let mut arguments_values: Vec> = Vec::with_capacity(arguments_len); 40 | for argument in statement.arguments.iter() { 41 | // No need to compare if the ordering argument is constants 42 | if argument.is_const() { 43 | arguments_values.push(Box::new(NullValue)); 44 | continue; 45 | } 46 | 47 | let value = &evaluate_expression(env, argument, titles, &row.values)?; 48 | arguments_values.push(value.to_owned()); 49 | } 50 | 51 | eval_map.insert(row_addr, arguments_values); 52 | } 53 | 54 | main_group.rows.sort_by(|a, b| { 55 | for arg_index in 0..arguments_len { 56 | let argument = &statement.arguments[arg_index]; 57 | // No need to compare if the ordering argument is constants 58 | if argument.is_const() { 59 | continue; 60 | } 61 | 62 | // Use the Memory address of A, B as Map keys 63 | let a_addr = a.values.as_ptr() as usize; 64 | let b_addr = b.values.as_ptr() as usize; 65 | 66 | // Get pre evaluated values from the eval map using addr as key, arg index as offset 67 | let a_value = &eval_map.get(&a_addr).unwrap()[arg_index]; 68 | let b_value = &eval_map.get(&b_addr).unwrap()[arg_index]; 69 | 70 | let null_ordering_policy = &statement.nulls_order_policies[arg_index]; 71 | if a_value.is_null() { 72 | return if null_ordering_policy.eq(&NullsOrderPolicy::NullsFirst) { 73 | Ordering::Less 74 | } else { 75 | Ordering::Greater 76 | }; 77 | } 78 | 79 | if b_value.is_null() { 80 | return if null_ordering_policy.eq(&NullsOrderPolicy::NullsFirst) { 81 | Ordering::Greater 82 | } else { 83 | Ordering::Less 84 | }; 85 | } 86 | 87 | // Calculate the ordering 88 | if let Some(order) = a_value.compare(b_value) { 89 | if order == Ordering::Equal { 90 | continue; 91 | } 92 | 93 | // Reverse the order if DESC order 94 | return if statement.sorting_orders[arg_index] == SortingOrder::Descending { 95 | order.reverse() 96 | } else { 97 | order 98 | }; 99 | } 100 | } 101 | 102 | Ordering::Equal 103 | }); 104 | 105 | Ok(()) 106 | } 107 | -------------------------------------------------------------------------------- /crates/gitql-engine/src/engine_output_into.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | 4 | use gitql_ast::statement::IntoStatement; 5 | use gitql_core::object::GitQLObject; 6 | use gitql_core::values::Value; 7 | 8 | pub(crate) fn execute_into_statement( 9 | statement: &IntoStatement, 10 | gitql_object: &mut GitQLObject, 11 | ) -> Result<(), String> { 12 | let mut buffer = String::new(); 13 | 14 | let line_terminated_by = &statement.lines_terminated; 15 | let field_terminated_by = &statement.fields_terminated; 16 | let enclosing = &statement.enclosed; 17 | 18 | // Headers 19 | let header = gitql_object.titles.join(field_terminated_by); 20 | buffer.push_str(&header); 21 | buffer.push_str(line_terminated_by); 22 | 23 | // Rows of the main group 24 | if let Some(main_group) = gitql_object.groups.first() { 25 | for row in &main_group.rows { 26 | let row_values: Vec = row 27 | .values 28 | .iter() 29 | .map(|r| value_to_string_with_optional_enclosing(r, enclosing)) 30 | .collect(); 31 | buffer.push_str(&row_values.join(field_terminated_by)); 32 | buffer.push_str(line_terminated_by); 33 | } 34 | } 35 | 36 | let file_result = File::create(statement.file_path.clone()); 37 | if let Err(error) = file_result { 38 | return Err(error.to_string()); 39 | } 40 | 41 | let mut file = file_result.ok().unwrap(); 42 | let write_result = file.write_all(buffer.as_bytes()); 43 | if let Err(error) = write_result { 44 | return Err(error.to_string()); 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | #[inline(always)] 51 | #[allow(clippy::borrowed_box)] 52 | fn value_to_string_with_optional_enclosing(value: &Box, enclosed: &String) -> String { 53 | if enclosed.is_empty() { 54 | return value.literal(); 55 | } 56 | format!("{}{}{}", enclosed, value.literal(), enclosed) 57 | } 58 | -------------------------------------------------------------------------------- /crates/gitql-engine/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod data_provider; 2 | pub mod engine; 3 | pub mod engine_distinct; 4 | pub mod engine_evaluator; 5 | pub mod engine_executor; 6 | pub mod engine_filter; 7 | pub mod engine_group; 8 | pub mod engine_join; 9 | pub mod engine_ordering; 10 | pub mod engine_output_into; 11 | pub mod engine_window_functions; 12 | -------------------------------------------------------------------------------- /crates/gitql-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gitql-parser" 3 | authors = ["AmrDeveloper"] 4 | version = "0.37.0" 5 | edition = "2021" 6 | description = "GitQL parser" 7 | repository = "https://github.com/amrdeveloper/gql/tree/main/crates/gitql-parser" 8 | license = "MIT" 9 | keywords = ["cli", "gql", "language", "git", "sql"] 10 | categories = ["command-line-utilities"] 11 | 12 | [dependencies] 13 | gitql-core = { path = "../gitql-core", version = "0.15.0" } 14 | gitql-ast = { path = "../gitql-ast", version = "0.34.0" } 15 | -------------------------------------------------------------------------------- /crates/gitql-parser/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Amr Hesham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/gitql-parser/README.md: -------------------------------------------------------------------------------- 1 |

GQL - Git Query Language Parser


2 | 3 |

4 | Crates.io 5 | Deps 6 | Release 7 | Docs 8 | GitHub release 9 | GitHub issues 10 | GitHub 11 | GitHub all releases 12 |

13 | 14 | 15 | ### License 16 | ``` 17 | MIT License 18 | 19 | Copyright (c) 2023 Amr Hesham 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | ``` 39 | -------------------------------------------------------------------------------- /crates/gitql-parser/src/context.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use gitql_ast::statement::AggregateValue; 4 | use gitql_ast::statement::WindowDefinition; 5 | use gitql_ast::statement::WindowValue; 6 | 7 | use crate::name_generator::NameGenerator; 8 | use crate::token::SourceLocation; 9 | 10 | #[derive(Default)] 11 | pub struct ParserContext { 12 | pub aggregations: HashMap, 13 | 14 | pub window_functions: HashMap, 15 | pub named_window_clauses: HashMap, 16 | 17 | pub selected_fields: Vec, 18 | pub hidden_selections: Vec, 19 | 20 | pub selected_tables: Vec, 21 | pub projection_names: Vec, 22 | pub projection_locations: Vec, 23 | 24 | pub name_alias_table: HashMap, 25 | pub name_generator: NameGenerator, 26 | 27 | pub is_single_value_query: bool, 28 | pub has_select_statement: bool, 29 | pub has_group_by_statement: bool, 30 | 31 | pub inside_selections: bool, 32 | pub inside_having: bool, 33 | pub inside_order_by: bool, 34 | pub inside_over_clauses: bool, 35 | } 36 | -------------------------------------------------------------------------------- /crates/gitql-parser/src/diagnostic.rs: -------------------------------------------------------------------------------- 1 | use crate::token::SourceLocation; 2 | 3 | /// In Memory representation for the Diagnostic element 4 | pub struct Diagnostic { 5 | label: String, 6 | message: String, 7 | location: Option, 8 | notes: Vec, 9 | helps: Vec, 10 | docs: Option, 11 | } 12 | 13 | impl Diagnostic { 14 | /// Create new instance of Diagnostic with required label and message 15 | #[must_use] 16 | pub fn new(label: &str, message: &str) -> Self { 17 | Diagnostic { 18 | label: label.to_owned(), 19 | message: message.to_owned(), 20 | location: None, 21 | notes: vec![], 22 | helps: vec![], 23 | docs: None, 24 | } 25 | } 26 | 27 | /// Create new instance of Diagnostic with label `Error` 28 | #[must_use] 29 | pub fn error(message: &str) -> Self { 30 | Diagnostic { 31 | label: "Error".to_owned(), 32 | message: message.to_owned(), 33 | location: None, 34 | notes: vec![], 35 | helps: vec![], 36 | docs: None, 37 | } 38 | } 39 | 40 | /// Create new instance of Diagnostic with label `Exception` 41 | #[must_use] 42 | pub fn exception(message: &str) -> Self { 43 | Diagnostic { 44 | label: "Exception".to_owned(), 45 | message: message.to_owned(), 46 | location: None, 47 | notes: vec![], 48 | helps: vec![], 49 | docs: None, 50 | } 51 | } 52 | 53 | /// Set location start and end from Location type 54 | pub fn with_location(mut self, location: SourceLocation) -> Self { 55 | self.location = Some(location); 56 | self 57 | } 58 | 59 | /// Set location start and end 60 | pub fn with_location_span(mut self, start: u32, end: u32) -> Self { 61 | self.location = Some(SourceLocation { 62 | line_start: 1, 63 | line_end: 1, 64 | column_start: start, 65 | column_end: end, 66 | }); 67 | self 68 | } 69 | 70 | /// Add new note to the current list 71 | pub fn add_note(mut self, note: &str) -> Self { 72 | self.notes.push(note.to_owned()); 73 | self 74 | } 75 | 76 | /// Add new help to the current list 77 | pub fn add_help(mut self, help: &str) -> Self { 78 | self.helps.push(help.to_owned()); 79 | self 80 | } 81 | 82 | /// Set Docs url 83 | pub fn with_docs(mut self, docs: &str) -> Self { 84 | self.docs = Some(docs.to_owned()); 85 | self 86 | } 87 | 88 | /// Return the Diagnostic label 89 | pub fn label(&self) -> &String { 90 | &self.label 91 | } 92 | 93 | /// Return the Diagnostic message 94 | pub fn message(&self) -> &String { 95 | &self.message 96 | } 97 | 98 | /// Return the diagnostic source location span 99 | pub fn location(&self) -> Option { 100 | self.location 101 | } 102 | 103 | /// Return the list of notes messages 104 | pub fn notes(&self) -> &Vec { 105 | &self.notes 106 | } 107 | 108 | /// Return the list of helps messages 109 | pub fn helps(&self) -> &Vec { 110 | &self.helps 111 | } 112 | 113 | /// Return the docs url if exists 114 | pub fn docs(&self) -> &Option { 115 | &self.docs 116 | } 117 | 118 | /// Get the Diagnostic as Box:: 119 | pub fn as_boxed(self) -> Box { 120 | Box::new(self) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/gitql-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod context; 2 | pub mod diagnostic; 3 | pub mod name_generator; 4 | pub mod name_similarity; 5 | pub mod type_checker; 6 | 7 | pub mod token; 8 | pub mod tokenizer; 9 | 10 | pub(crate) mod parse_cast; 11 | pub(crate) mod parse_comparisons; 12 | pub(crate) mod parse_function_call; 13 | pub(crate) mod parse_interval; 14 | pub(crate) mod parse_into; 15 | pub(crate) mod parse_ordering; 16 | pub(crate) mod parse_type; 17 | pub mod parser; 18 | -------------------------------------------------------------------------------- /crates/gitql-parser/src/name_generator.rs: -------------------------------------------------------------------------------- 1 | static COLUMN_NAME_PREFIX: &str = "column_"; 2 | static HIDDEN_NAME_PREFIX: &str = "_@temp_"; 3 | 4 | /// Component to generate name for visible and hidden columns with number as prefix started from 0 5 | #[derive(Default)] 6 | pub struct NameGenerator { 7 | column_name_number: usize, 8 | temp_name_number: usize, 9 | } 10 | 11 | impl NameGenerator { 12 | /// Generate name for visible column 13 | pub fn generate_column_name(&mut self) -> String { 14 | let name = format!("{}{}", COLUMN_NAME_PREFIX, self.column_name_number); 15 | self.column_name_number += 1; 16 | name 17 | } 18 | 19 | pub fn generate_temp_name(&mut self) -> String { 20 | let name = format!("{}{}", HIDDEN_NAME_PREFIX, self.temp_name_number); 21 | self.temp_name_number += 1; 22 | name 23 | } 24 | 25 | /// Reset the name counter to start from 0 in new session 26 | pub fn reset_numbers(&mut self) { 27 | self.column_name_number = 0; 28 | self.temp_name_number = 0; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/gitql-parser/src/name_similarity.rs: -------------------------------------------------------------------------------- 1 | const MIN_DISTANCE: usize = 2; 2 | 3 | pub(crate) fn find_closeest_string(target: &str, candidates: &[&&str]) -> Option { 4 | if candidates.is_empty() { 5 | return None; 6 | } 7 | 8 | let mut closest_match: Option = None; 9 | let mut min_distance = usize::MAX; 10 | 11 | for candidate in candidates { 12 | let distance = levenshtein_distance(target, candidate); 13 | if distance < min_distance { 14 | min_distance = distance; 15 | closest_match = Some(candidate.to_string()); 16 | } 17 | } 18 | 19 | if min_distance <= MIN_DISTANCE { 20 | return closest_match; 21 | } 22 | None 23 | } 24 | 25 | fn levenshtein_distance(s1: &str, s2: &str) -> usize { 26 | let s1_chars: Vec = s1.chars().collect(); 27 | let s2_chars: Vec = s2.chars().collect(); 28 | 29 | let s1_len = s1_chars.len(); 30 | let s2_len = s2_chars.len(); 31 | 32 | let vec1_len = s1_len + 1; 33 | let vec12_len = s2_len + 1; 34 | 35 | let mut matrix = vec![vec![0; vec12_len]; vec1_len]; 36 | for (i, vector) in matrix.iter_mut().enumerate().take(vec1_len) { 37 | vector[0] = i; 38 | } 39 | 40 | for j in 0..vec12_len { 41 | matrix[0][j] = j; 42 | } 43 | 44 | for i in 1..vec1_len { 45 | for j in 1..vec12_len { 46 | let cost = if s1_chars[i - 1] == s2_chars[j - 1] { 47 | 0 48 | } else { 49 | 1 50 | }; 51 | 52 | matrix[i][j] = (matrix[i - 1][j] + 1) 53 | .min(matrix[i][j - 1] + 1) 54 | .min(matrix[i - 1][j - 1] + cost); 55 | } 56 | } 57 | 58 | matrix[s1_len][s2_len] 59 | } 60 | -------------------------------------------------------------------------------- /crates/gitql-parser/src/parse_cast.rs: -------------------------------------------------------------------------------- 1 | use gitql_ast::expression::CastExpr; 2 | use gitql_ast::expression::Expr; 3 | use gitql_ast::types::DataType; 4 | use gitql_core::environment::Environment; 5 | 6 | use crate::context::ParserContext; 7 | use crate::diagnostic::Diagnostic; 8 | use crate::parse_type::parse_type; 9 | use crate::parser::consume_token_or_error; 10 | use crate::parser::parse_expression; 11 | use crate::parser::parse_index_or_slice_expression; 12 | use crate::token::SourceLocation; 13 | use crate::token::Token; 14 | use crate::token::TokenKind; 15 | 16 | pub(crate) fn parse_cast_operator_expression( 17 | context: &mut ParserContext, 18 | env: &mut Environment, 19 | tokens: &[Token], 20 | position: &mut usize, 21 | ) -> Result, Box> { 22 | let expr = parse_index_or_slice_expression(context, env, tokens, position)?; 23 | 24 | if *position < tokens.len() && tokens[*position].kind == TokenKind::ColonColon { 25 | // Consume `::` Token 26 | let colon_colon_token = &tokens[*position]; 27 | *position += 1; 28 | 29 | let target_type = parse_type(env, tokens, position)?; 30 | return cast_expression_or_error(expr, target_type, colon_colon_token.location); 31 | } 32 | 33 | Ok(expr) 34 | } 35 | 36 | pub(crate) fn parse_cast_call_expression( 37 | context: &mut ParserContext, 38 | env: &mut Environment, 39 | tokens: &[Token], 40 | position: &mut usize, 41 | ) -> Result, Box> { 42 | let cast_token_location = 43 | consume_token_or_error(tokens, position, TokenKind::Cast, "Expect 'CAST' Keyword")? 44 | .location; 45 | 46 | consume_token_or_error( 47 | tokens, 48 | position, 49 | TokenKind::LeftParen, 50 | "Expect '(' after 'CAST' Keyword", 51 | )?; 52 | 53 | let expr = parse_expression(context, env, tokens, position)?; 54 | 55 | consume_token_or_error( 56 | tokens, 57 | position, 58 | TokenKind::As, 59 | "Expect 'AS' keyword after 'CAST' expression value", 60 | )?; 61 | 62 | let target_type = parse_type(env, tokens, position)?; 63 | 64 | consume_token_or_error( 65 | tokens, 66 | position, 67 | TokenKind::RightParen, 68 | "Expect ')' at the end of 'CAST' expression", 69 | )?; 70 | 71 | cast_expression_or_error(expr, target_type, cast_token_location) 72 | } 73 | 74 | fn cast_expression_or_error( 75 | expr: Box, 76 | target_type: Box, 77 | location: SourceLocation, 78 | ) -> Result, Box> { 79 | let value_type = expr.expr_type(); 80 | let value_expected_types = value_type.can_perform_explicit_cast_op_to(); 81 | 82 | // If it's supported to cast this value to result type, just return CastExpr 83 | if value_expected_types.contains(&target_type) { 84 | return Ok(Box::new(CastExpr { 85 | value: expr, 86 | result_type: target_type, 87 | })); 88 | } 89 | 90 | // Check if it possible to implicit cast the value to one of the expected type of result type 91 | // then Cast from expected type to the result type 92 | // Examples: Cast("true" as Int) can be casted as Text -> Bool -> Int 93 | let expected_types = target_type.can_perform_explicit_cast_op_to(); 94 | for expected_type in expected_types { 95 | if expected_type.has_implicit_cast_from(&expr) { 96 | let casting = Box::new(CastExpr { 97 | value: expr, 98 | result_type: expected_type.clone(), 99 | }); 100 | 101 | return Ok(Box::new(CastExpr { 102 | value: casting, 103 | result_type: target_type, 104 | })); 105 | } 106 | } 107 | 108 | Err(Diagnostic::error(&format!( 109 | "Unsupported `CAST` operator from type `{}` to type `{}`", 110 | value_type.literal(), 111 | target_type.literal(), 112 | )) 113 | .with_location(location) 114 | .as_boxed()) 115 | } 116 | -------------------------------------------------------------------------------- /crates/gitql-parser/src/parse_type.rs: -------------------------------------------------------------------------------- 1 | use gitql_ast::types::array::ArrayType; 2 | use gitql_ast::types::DataType; 3 | use gitql_core::environment::Environment; 4 | 5 | use crate::diagnostic::Diagnostic; 6 | use crate::parser::calculate_safe_location; 7 | use crate::parser::consume_conditional_token_or_errors; 8 | use crate::token::Token; 9 | use crate::token::TokenKind; 10 | 11 | pub(crate) fn parse_type( 12 | env: &mut Environment, 13 | tokens: &[Token], 14 | position: &mut usize, 15 | ) -> Result, Box> { 16 | let mut data_type = parse_primitive_type(env, tokens, position)?; 17 | 18 | while *position < tokens.len() { 19 | match tokens[*position].kind { 20 | TokenKind::LeftBracket => data_type = parse_array_type(tokens, position, data_type)?, 21 | _ => break, 22 | } 23 | } 24 | 25 | Ok(data_type) 26 | } 27 | 28 | fn parse_array_type( 29 | tokens: &[Token], 30 | position: &mut usize, 31 | base_type: Box, 32 | ) -> Result, Box> { 33 | // Make sure there is '[' After the base DataType 34 | if *position >= tokens.len() || tokens[*position].kind != TokenKind::LeftBracket { 35 | return Err(Diagnostic::error("Expect [ After Base DataType") 36 | .with_location(calculate_safe_location(tokens, *position - 1)) 37 | .as_boxed()); 38 | } 39 | 40 | // Consume '[' token 41 | *position += 1; 42 | 43 | // Make sure there is ']' After the base DataType 44 | if *position >= tokens.len() || tokens[*position].kind != TokenKind::RightBracket { 45 | return Err(Diagnostic::error("Expect ']' After '[' in Array DataType") 46 | .with_location(calculate_safe_location(tokens, *position - 1)) 47 | .as_boxed()); 48 | } 49 | 50 | // Consume ']' token 51 | *position += 1; 52 | 53 | Ok(Box::new(ArrayType { base: base_type })) 54 | } 55 | 56 | fn parse_primitive_type( 57 | env: &mut Environment, 58 | tokens: &[Token], 59 | position: &mut usize, 60 | ) -> Result, Box> { 61 | // Parse `Symbol` token that represent DataType name 62 | let type_name_token = consume_conditional_token_or_errors( 63 | tokens, 64 | position, 65 | |token| matches!(token.kind, TokenKind::Symbol(_)), 66 | "Expect Symbol to represent Type name", 67 | )?; 68 | 69 | let type_literal = type_name_token.to_string(); 70 | if let Some(data_type) = env.types_table.lookup(type_literal.as_str()) { 71 | return Ok(data_type); 72 | } 73 | 74 | Err(Diagnostic::error(&format!( 75 | "No available type in TypeTable with name `{}`", 76 | type_literal 77 | )) 78 | .with_location(type_name_token.location) 79 | .as_boxed()) 80 | } 81 | -------------------------------------------------------------------------------- /crates/gitql-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gitql-std" 3 | authors = ["AmrDeveloper"] 4 | version = "0.15.0" 5 | edition = "2021" 6 | description = "GitQL Standard and Aggregation functions" 7 | repository = "https://github.com/amrdeveloper/gql/tree/main/crates/gitql-cli" 8 | license = "MIT" 9 | keywords = ["cli", "gql", "language", "git", "sql"] 10 | categories = ["command-line-utilities"] 11 | 12 | [dependencies] 13 | gitql-core = { path = "../gitql-core", version = "0.15.0" } 14 | gitql-ast = { path = "../gitql-ast", version = "0.34.0" } 15 | chrono = { workspace = true } 16 | regex = { workspace = true } 17 | rand = { workspace = true } 18 | uuid = { workspace = true } 19 | -------------------------------------------------------------------------------- /crates/gitql-std/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Amr Hesham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/gitql-std/README.md: -------------------------------------------------------------------------------- 1 |

GQL - Git Query Language Standard and Aggregation functions


2 | 3 |

4 | Crates.io 5 | Deps 6 | Release 7 | Docs 8 | GitHub release 9 | GitHub issues 10 | GitHub 11 | GitHub all releases 12 |

13 | 14 | 15 | ### License 16 | ``` 17 | MIT License 18 | 19 | Copyright (c) 2023 Amr Hesham 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | ``` 39 | -------------------------------------------------------------------------------- /crates/gitql-std/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod aggregation; 2 | pub mod array; 3 | pub mod datetime; 4 | pub mod general; 5 | pub mod meta_types; 6 | pub mod number; 7 | pub mod range; 8 | pub mod regex; 9 | pub mod standard; 10 | pub mod text; 11 | pub mod window; 12 | -------------------------------------------------------------------------------- /crates/gitql-std/src/meta_types.rs: -------------------------------------------------------------------------------- 1 | use gitql_ast::types::array::ArrayType; 2 | use gitql_ast::types::DataType; 3 | 4 | /// Returns the type of of first element 5 | /// (T1, T2, ...) -> T1 6 | #[inline(always)] 7 | pub fn first_element_type(elements: &[Box]) -> Box { 8 | elements[0].clone() 9 | } 10 | 11 | /// Returns the type of second element 12 | /// (T1, T2, ...) -> T2 13 | #[inline(always)] 14 | pub fn second_element_type(elements: &[Box]) -> Box { 15 | elements[0].clone() 16 | } 17 | 18 | /// Returns Array type of the passed element type 19 | /// T -> Array 20 | #[inline(always)] 21 | pub fn array_of_type(element_type: Box) -> Box { 22 | Box::new(ArrayType::new(element_type.clone())) 23 | } 24 | 25 | /// Returns element type of passed Array type 26 | /// Array -> T 27 | #[inline(always)] 28 | pub fn array_element_type(array: Box) -> Box { 29 | if let Some(other_array) = array.as_any().downcast_ref::() { 30 | return other_array.base.clone(); 31 | } 32 | panic!("Expect Array type") 33 | } 34 | -------------------------------------------------------------------------------- /crates/gitql-std/src/range/mod.rs: -------------------------------------------------------------------------------- 1 | use gitql_ast::types::any::AnyType; 2 | use gitql_ast::types::boolean::BoolType; 3 | use gitql_ast::types::date::DateType; 4 | use gitql_ast::types::datetime::DateTimeType; 5 | use gitql_ast::types::integer::IntType; 6 | use gitql_ast::types::range::RangeType; 7 | use gitql_core::signature::Signature; 8 | use gitql_core::signature::StandardFunction; 9 | use gitql_core::values::boolean::BoolValue; 10 | use gitql_core::values::range::RangeValue; 11 | use gitql_core::values::Value; 12 | 13 | use std::collections::HashMap; 14 | 15 | #[inline(always)] 16 | pub fn register_std_range_functions(map: &mut HashMap<&'static str, StandardFunction>) { 17 | map.insert("int4range", int4range); 18 | map.insert("daterange", daterange); 19 | map.insert("tsrange", tsrange); 20 | map.insert("isempty", isempty); 21 | } 22 | 23 | #[inline(always)] 24 | pub fn register_std_range_function_signatures(map: &mut HashMap<&'static str, Signature>) { 25 | map.insert( 26 | "int4range", 27 | Signature { 28 | parameters: vec![Box::new(IntType), Box::new(IntType)], 29 | return_type: Box::new(RangeType { 30 | base: Box::new(IntType), 31 | }), 32 | }, 33 | ); 34 | map.insert( 35 | "daterange", 36 | Signature { 37 | parameters: vec![Box::new(DateType), Box::new(DateType)], 38 | return_type: Box::new(RangeType { 39 | base: Box::new(DateType), 40 | }), 41 | }, 42 | ); 43 | map.insert( 44 | "tsrange", 45 | Signature { 46 | parameters: vec![Box::new(DateTimeType), Box::new(DateTimeType)], 47 | return_type: Box::new(RangeType { 48 | base: Box::new(DateTimeType), 49 | }), 50 | }, 51 | ); 52 | map.insert( 53 | "isempty", 54 | Signature { 55 | parameters: vec![Box::new(RangeType { 56 | base: Box::new(AnyType), 57 | })], 58 | return_type: Box::new(BoolType), 59 | }, 60 | ); 61 | } 62 | 63 | pub fn int4range(inputs: &[Box]) -> Box { 64 | Box::new(RangeValue { 65 | start: inputs[0].clone(), 66 | end: inputs[1].clone(), 67 | base_type: Box::new(IntType), 68 | }) 69 | } 70 | 71 | pub fn daterange(inputs: &[Box]) -> Box { 72 | Box::new(RangeValue { 73 | start: inputs[0].clone(), 74 | end: inputs[1].clone(), 75 | base_type: Box::new(DateType), 76 | }) 77 | } 78 | 79 | pub fn tsrange(inputs: &[Box]) -> Box { 80 | Box::new(RangeValue { 81 | start: inputs[0].clone(), 82 | end: inputs[1].clone(), 83 | base_type: Box::new(DateTimeType), 84 | }) 85 | } 86 | 87 | pub fn isempty(inputs: &[Box]) -> Box { 88 | let range = inputs[0].as_range().unwrap(); 89 | Box::new(BoolValue { 90 | value: range.0.equals(&range.1), 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /crates/gitql-std/src/standard.rs: -------------------------------------------------------------------------------- 1 | use crate::array::*; 2 | use crate::datetime::*; 3 | use crate::general::*; 4 | use crate::number::*; 5 | use crate::range::*; 6 | use crate::regex::*; 7 | use crate::text::*; 8 | 9 | use gitql_core::signature::Signature; 10 | use gitql_core::signature::StandardFunction; 11 | use std::collections::HashMap; 12 | use std::sync::OnceLock; 13 | 14 | pub fn standard_functions() -> &'static HashMap<&'static str, StandardFunction> { 15 | static HASHMAP: OnceLock> = OnceLock::new(); 16 | HASHMAP.get_or_init(|| { 17 | let mut map: HashMap<&'static str, StandardFunction> = HashMap::new(); 18 | register_std_text_functions(&mut map); 19 | register_std_datetime_functions(&mut map); 20 | register_std_number_functions(&mut map); 21 | register_std_general_functions(&mut map); 22 | register_std_regex_functions(&mut map); 23 | register_std_array_functions(&mut map); 24 | register_std_range_functions(&mut map); 25 | map 26 | }) 27 | } 28 | 29 | pub fn standard_function_signatures() -> HashMap<&'static str, Signature> { 30 | let mut map: HashMap<&'static str, Signature> = HashMap::new(); 31 | register_std_text_function_signatures(&mut map); 32 | register_std_datetime_function_signatures(&mut map); 33 | register_std_number_function_signatures(&mut map); 34 | register_std_general_function_signatures(&mut map); 35 | register_std_regex_function_signatures(&mut map); 36 | register_std_array_function_signatures(&mut map); 37 | register_std_range_function_signatures(&mut map); 38 | map 39 | } 40 | -------------------------------------------------------------------------------- /crates/gitql-std/src/window.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::OnceLock; 3 | 4 | use gitql_ast::types::any::AnyType; 5 | use gitql_ast::types::dynamic::DynamicType; 6 | use gitql_ast::types::integer::IntType; 7 | use gitql_core::signature::Signature; 8 | use gitql_core::signature::WindowFunction; 9 | use gitql_core::values::integer::IntValue; 10 | use gitql_core::values::null::NullValue; 11 | use gitql_core::values::Value; 12 | 13 | use crate::meta_types::first_element_type; 14 | 15 | pub fn window_functions() -> &'static HashMap<&'static str, WindowFunction> { 16 | static HASHMAP: OnceLock> = OnceLock::new(); 17 | HASHMAP.get_or_init(|| { 18 | let mut map: HashMap<&'static str, WindowFunction> = HashMap::new(); 19 | map.insert("first_value", window_first_value); 20 | map.insert("nth_value", window_nth_value); 21 | map.insert("last_value", window_last_value); 22 | map.insert("row_number", window_row_number); 23 | map 24 | }) 25 | } 26 | 27 | pub fn window_function_signatures() -> HashMap<&'static str, Signature> { 28 | let mut map: HashMap<&'static str, Signature> = HashMap::new(); 29 | map.insert( 30 | "first_value", 31 | Signature { 32 | parameters: vec![Box::new(AnyType)], 33 | return_type: Box::new(DynamicType { 34 | function: first_element_type, 35 | }), 36 | }, 37 | ); 38 | 39 | map.insert( 40 | "nth_value", 41 | Signature { 42 | parameters: vec![Box::new(AnyType), Box::new(IntType)], 43 | return_type: Box::new(DynamicType { 44 | function: first_element_type, 45 | }), 46 | }, 47 | ); 48 | 49 | map.insert( 50 | "last_value", 51 | Signature { 52 | parameters: vec![Box::new(AnyType)], 53 | return_type: Box::new(DynamicType { 54 | function: first_element_type, 55 | }), 56 | }, 57 | ); 58 | 59 | map.insert( 60 | "row_number", 61 | Signature { 62 | parameters: vec![], 63 | return_type: Box::new(IntType), 64 | }, 65 | ); 66 | map 67 | } 68 | 69 | pub fn window_first_value(frame: &[Vec>]) -> Vec> { 70 | let frame_len = frame.len(); 71 | let first_value = &frame[0][0]; 72 | let mut values = Vec::with_capacity(frame_len); 73 | for _ in 0..frame_len { 74 | values.push(first_value.clone()); 75 | } 76 | values 77 | } 78 | 79 | pub fn window_nth_value(frame: &[Vec>]) -> Vec> { 80 | let frame_len = frame.len(); 81 | let index = frame[0][1].as_int().unwrap(); 82 | 83 | let mut values: Vec> = Vec::with_capacity(frame_len); 84 | for _ in 0..frame_len { 85 | if index < 0 || index as usize >= frame_len { 86 | values.push(Box::new(NullValue)); 87 | } else { 88 | values.push(frame[index as usize][0].clone()); 89 | }; 90 | } 91 | 92 | values 93 | } 94 | 95 | pub fn window_last_value(frame: &[Vec>]) -> Vec> { 96 | let frame_len = frame.len(); 97 | let last_value = &frame[frame_len - 1][0]; 98 | let mut values = Vec::with_capacity(frame_len); 99 | for _ in 0..frame_len { 100 | values.push(last_value.clone()); 101 | } 102 | values 103 | } 104 | 105 | pub fn window_row_number(frame: &[Vec>]) -> Vec> { 106 | let frame_len = frame.len(); 107 | let mut values: Vec> = Vec::with_capacity(frame_len); 108 | for i in 0..frame_len { 109 | let num = i as i64 + 1; 110 | values.push(Box::new(IntValue { value: num })); 111 | } 112 | values 113 | } 114 | -------------------------------------------------------------------------------- /docs/assets/gql_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/expression/access.md: -------------------------------------------------------------------------------- 1 | ### Member Access Expression 2 | 3 | GitQL support Composite Type inspired by PostgreSQL, to access member of composite type 4 | you should use `.` operator like in any programming language but with putting the composite value in `()` like 5 | `(user).username` to make it different from table column `table.column` -------------------------------------------------------------------------------- /docs/expression/array.md: -------------------------------------------------------------------------------- 1 | ### Array value Expression 2 | 3 | Array expression can be created using the `ARRAY` keyword followed by a list of expression between `[` and `]`. 4 | 5 | ```sql 6 | SELECT ARRAY[1, 2, 3]; 7 | SELECT ARRAY[ARRAY[1, 2, 3], ARRAY[4, 5, 6], ARRAY[7, 8, 9]]; 8 | ``` 9 | 10 | Or you can write the list of expressions directly with `[` and `]` 11 | 12 | ```sql 13 | SELECT [1, 2, 3]; 14 | SELECT [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; 15 | ``` 16 | 17 | ### Slice Expression 18 | 19 | Slice expression can be used to return a slice from array from `[start:end`. 20 | 21 | ```sql 22 | SELECT [1, 2, 3][1:2]; 23 | SELECT [[1, 2, 3], [4, 5, 6], [7, 8, 9]][1:2]; 24 | ``` 25 | 26 | Slice expression can be used also with 1 as start range to return a slice from array from 1 to the end. 27 | 28 | ```sql 29 | SELECT [1, 2, 3][:2]; 30 | SELECT [[1, 2, 3], [4, 5, 6], [7, 8, 9]][:3]; 31 | ``` 32 | 33 | Slice expression can be used also with start only range to return a slice from array from start to length of array. 34 | 35 | ```sql 36 | SELECT [1, 2, 3][1:]; 37 | SELECT [[1, 2, 3], [4, 5, 6], [7, 8, 9]][2:]; 38 | ``` -------------------------------------------------------------------------------- /docs/expression/call.md: -------------------------------------------------------------------------------- 1 | ### Call expression 2 | 3 | ## 1. Standard Function Calls 4 | 5 | Standard functions call operate on individual rows and return a single value per row, with syntax similar to most programming languages for example 6 | 7 | ```sql 8 | LEN(name) 9 | LOWER(author_name) 10 | ``` 11 | 12 | ## 2. Aggregate Function Calls 13 | 14 | Aggregate functions call has the same syntax like standard function call but it's operate on a set of rows (a group) and return a single value for the entire group. They are often used with the `GROUP BY` clause, the value of aggregation function can be used only after group by statement. 15 | 16 | ```sql 17 | SELECT author_name, COUNT(author_name) AS commit_num FROM commits GROUP BY author_name, author_email ORDER BY commit_num DESC LIMIT 10 18 | ``` 19 | 20 | ## 3. Window functions 21 | 22 | Window functions perform calculations across a set of rows that are related to the current row. Unlike aggregate functions with GROUP BY, window functions do not collapse rows into a single output row. Instead, they return a value for each input row based on a "window" of related rows, 23 | in window function call you must to explicit define `OVER` clauses even if it empty, also you can use aggregation function as window function. 24 | 25 | ```sql 26 | SELECT emp_name, dep_name, ROW_NUMBER() OVER(PARTITION BY dep_name) AS row_number FROM emp_salaries 27 | 28 | SELECT emp_name, 29 | dep_name, 30 | ROW_NUMBER() OVER partition_dep_order_salary_des AS row_number_per_department, 31 | MIN(salary) OVER partition_dep_order_salary_des AS min_salary_per_department, 32 | MAX(salary) OVER partition_dep_order_salary_des AS max_salary_per_department 33 | FROM emp_salaries 34 | WINDOW partition_dep_order_salary_des AS (PARTITION BY dep_name ORDER BY salary DESC) 35 | ORDER BY dep_name ASC NULLS LAST; 36 | ``` -------------------------------------------------------------------------------- /docs/expression/case.md: -------------------------------------------------------------------------------- 1 | ### Case expression 2 | 3 | Case expression is similar to Switch Expression in many languages, it's return the value of the first branch that has condition evaluated to true, if not branch found it will return the default value 4 | 5 | ```sql 6 | SELECT name FROM branches WHERE (CASE WHEN isRemote THEN 1 ELSE 0 END) > 0 7 | ``` -------------------------------------------------------------------------------- /docs/expression/cast.md: -------------------------------------------------------------------------------- 1 | ### Cast expression 2 | 3 | In GitQL there are two types of casting, Explicit and Implicit casting 4 | 5 | #### Implicit Casting 6 | 7 | Implicit casting is performed without the need from you to write cast operator or function for example 8 | 9 | ```sql 10 | SELECT True = 't' 11 | ``` 12 | 13 | In this case the engine perform implicit cast from Text 't' to become boolean 'true' so it's end up with 14 | 15 | ```sql 16 | SELECT True = True 17 | ``` 18 | 19 | The same is performed when you write Date, Time or DateTime as String and pass it to function that accept Date. 20 | 21 | #### Explicit Casting 22 | 23 | Implicit casting can handle some cases when the value is const and has specific pattern, but in some cases you want for example 24 | to cast Float to Int or Int to Float after the value is evaluated or provided from real data, in this case you need to explicit ask the engine 25 | to case this value to another type, for example 26 | 27 | ```SQL 28 | SELECT CAST(commits_count AS Real); 29 | ``` 30 | 31 | Instead of using the above syntax, we can also use the following condensed syntax: 32 | 33 | ```SQL 34 | SELECT commits_count::Real; 35 | ``` -------------------------------------------------------------------------------- /docs/expression/index.md: -------------------------------------------------------------------------------- 1 | GitQL Expressions 2 | 3 | - [Binary expressions](binary.md). 4 | - [Unary expressions](unary.md). 5 | - [Case expression](case.md). 6 | - [Cast expression](cast.md). 7 | - [Array expression](array.md). 8 | - [Access Member](access.md). 9 | - [Call expression](call.md). 10 | - [Interval expression](interval.md). -------------------------------------------------------------------------------- /docs/expression/interval.md: -------------------------------------------------------------------------------- 1 | ### Interval expression 2 | 3 | The Interval is another type of data type used to store and deploy Time in years, months, days, hours, minutes, seconds. And the years, months and days, hours and minutes values are integers values, whereas the second's field can be the fractions values. 4 | 5 | Inspired by PostgreSQL, interval data type value involves 16 bytes storage size, which helps to store a period with the acceptable range from -178000000 years to 178000000 years. 6 | 7 | #### Examples 8 | 9 | ```SQL 10 | SELECT INTERVAL '1 years 1 days' = INTERVAL '1 years 2 days'; 11 | SELECT INTERVAL '1 years 1 days' != INTERVAL '1 years 2 days'; 12 | SELECT INTERVAL '1 years 1 days' + INTERVAL '1 years 2 days'; 13 | SELECT INTERVAL '1 years 1 days' - INTERVAL '1 years 2 days'; 14 | SELECT INTERVAL '1 years 1 days' * 2; 15 | SELECT INTERVAL '2 years 2 days' / 2; 16 | ``` -------------------------------------------------------------------------------- /docs/expression/unary.md: -------------------------------------------------------------------------------- 1 | The unary expression is an expression the prefixed with operators 2 | 3 | ### Prefix Unary Expression 4 | - `!` takes truth to falsity and vice versa. It is typically used with boolean 5 | 6 | ```sql 7 | SELECT * FROM branches WHERE !is_remote 8 | SELECT * FROM branches WHERE !is_head 9 | ``` 10 | 11 | - `-` negates the value of the operand. 12 | 13 | ```sql 14 | SELECT * FROM branches WHERE commit_count > -1 15 | ``` -------------------------------------------------------------------------------- /docs/functions/aggregations.md: -------------------------------------------------------------------------------- 1 | ### Aggregations functions 2 | 3 | An aggregate function in GitQL performs a calculation on multiple values and returns a single value 4 | 5 | | Name | Parameters | Return | Description | 6 | | ------------ | ---------- | ---------- | ----------------------------------------------------------------- | 7 | | MAX | ANY | Any | Return maximum value of it for all elements until the current one | 8 | | MIN | ANY | Any | Return minimum value of it for all elements until the current one | 9 | | SUM | Number | Number | Return the sum of items in a group. | 10 | | AVG | Number | Number | Return the average of items in a group | 11 | | COUNT | ANY? | Any | Return the number of items in a group | 12 | | GROUP_CONCAT | ...Any | Text | Return string with concatenated non-NULL value from a group | 13 | | BOOL_AND | Boolean | Boolean | Return true if all input values are true, otherwise false | 14 | | BOOL_OR | Boolean | Boolean | Return true if at least one input value is true, otherwise false | 15 | | BIT_AND | Integer | Integer | Return bitwise AND of all non-null input values, or null if none | 16 | | BIT_OR | Integer | Integer | Return bitwise OR of all non-null input values, or null if none | 17 | | BIT_XOR | Integer | Integer | Return bitwise XOR of all non-null input values, or null if none | 18 | | ARRAY_AGG | Any | Array(Any) | Return an array of values | 19 | -------------------------------------------------------------------------------- /docs/functions/array.md: -------------------------------------------------------------------------------- 1 | ### Array operators 2 | 3 | | Operator | Arguments | Description | 4 | | -------- | -------------------- | ----------- | 5 | | @> | (Array, T) | Contains | 6 | | <@ | (T, Array) | Contain by | 7 | | && | (Array, Array) | Overlap | 8 | 9 | ### Array functions 10 | 11 | | Name | Parameters | Return | Description | 12 | | --------------- | --------------- | -------------- | --------------------------------------------------------------------------------- | 13 | | ARRAY_APPEND | Array, Any | Array | Append element to the end of the array. | 14 | | ARRAY_PREPEND | Any, Array | Array | Append element to the start of the array. | 15 | | ARRAY_REMOVE | Array, Any | Array | Remove element from the array. | 16 | | ARRAY_CAT | Array, Array | Array | Concatenates two arrays with the same type. | 17 | | ARRAY_LENGTH | Array | Integer | Return the length of Array. | 18 | | ARRAY_SHUFFLE | Array | Array | Return Randomly shuffles the first dimension of the array. | 19 | | ARRAY_POSITION | Array, Any | Integer | Return the position of element in array or NULL if not found. | 20 | | ARRAY_POSITIONS | Array, Any | Array | Return the an array of positions of element in array. | 21 | | ARRAY_DIMS | Array | Text | Returns a text representation of the array's dimensions. | 22 | | ARRAY_REPLACE | Array, Any, Any | Array | Replaces each array element equal to the second argument with the third argument. | 23 | | TRIM_ARRAY | Array, Integer | Array | Remove the last n elements from the array. | -------------------------------------------------------------------------------- /docs/functions/comparison.md: -------------------------------------------------------------------------------- 1 | ### Comparison Operators 2 | 3 | | Operator | Description | 4 | | -------- | --------------------- | 5 | | < | Less then | 6 | | > | Greater than | 7 | | <= | Less than or equal | 8 | | >= | Greater then or equal | 9 | | = | Equal | 10 | | <> or != | Not equal | 11 | | <=> | Null Safe equal | -------------------------------------------------------------------------------- /docs/functions/datetime.md: -------------------------------------------------------------------------------- 1 | ### Date, Time and DateTime operators 2 | 3 | | Operator | Arguments | Description | 4 | | -------- | --------------- | ----------------------------------- | 5 | | + | (Date, Integer) | Add a number of days to a date | 6 | | - | (Date, Integer) | Subtract a number of days to a date | 7 | 8 | ### Date, Time and DateTime functions 9 | 10 | | Name | Parameters | Return | Description | 11 | | ----------------- | ------------------------- | -------- | ----------------------------------------------------------------------------- | 12 | | Date | DateTime | Date | Extracts the date part from a datetime expression. | 13 | | CURRENT_TIME | | Time | Return current time in `HH:MM:SS` format. | 14 | | CURRENT_DATE | | Date | Return current date in `YYYY-MM-DD` format. | 15 | | CURRENT_TIMESTAMP | | DateTime | Return current date time in `YYYY-MM-DD HH:MM:SS` format. | 16 | | MAKEDATE | Integer, Integer | Date | Create and return a date based on a year and a number of days. | 17 | | MAKETIME | Integer, Integer, Integer | Time | Create and return a time value based on an hour, minute, and second value. | 18 | | NOW | | DateTime | Return current date time in `YYYY-MM-DD HH:MM:SS` format. | 19 | | Day | Date | Integer | Returns the index of the day (1 to 31) in the date. | 20 | | DAYNAME | Date | Text | Returns the name of the day given a timestamp. | 21 | | MONTHNAME | Date | Text | Returns the name of the month given a timestamp. | 22 | | HOUR | DateTime | Integer | Returns the hour part of a datetime. | 23 | | MINUTE | DateTime | Integer | Returns the minute part of a datetime. | 24 | | ISDATE | Any | Boolean | Return TRUE if the argument type is Date. | 25 | | DAYOFWEEK | Date | Integer | Returns the day of the week for a given date (a number from 1 to 7) | 26 | | DAYOFMONTH | Date | Integer | Returns the day of the month for a given date (a number from 1 to 31) | 27 | | DAYOFYEAR | Date | Integer | Returns the day of the year for a given date (a number from 1 to 366) | 28 | | WEEKOFYEAR | Date | Integer | Returns the week number for a given date (a number from 1 to 53). | 29 | | QUARTER | Date | Integer | Returns the quarter of the year for a given date value (a number from 1 to 4) | 30 | | YEAR | Date | Integer | Returns the year part of the date | 31 | | MONTH | Date | Integer | Returns the month part of the date (a number from 1 to 12) | 32 | | WEEKDAY | Date | Integer | Returns the weekday number of the date (from 0 monday to 6 sunday) | 33 | | TO_DAYS | Date | Integer | Returns the number of days between a date and date "0000-00-00" | 34 | | LAST_DAY | Date | Date | Returns the last day of the month for a given date | 35 | | YEARWEEK | Date | Text | Returns the year and week number (a number from 0 to 53) for a given date | 36 | -------------------------------------------------------------------------------- /docs/functions/index.md: -------------------------------------------------------------------------------- 1 | GitQL Standard functions and operators 2 | 3 | - [Logical](logical.md). 4 | - [comparison](comparison.md). 5 | - [Mathematical](math.md). 6 | - [Text](string.md). 7 | - [Date/Time](datetime.md). 8 | - [Regex](regex.md). 9 | - [Array](array.md). 10 | - [Range](range.md). 11 | - [Interval](interval.md). 12 | - [Window] (window.md) 13 | - [Aggregations](aggregations.md). 14 | - [General](other.md). 15 | -------------------------------------------------------------------------------- /docs/functions/interval.md: -------------------------------------------------------------------------------- 1 | ### Interval Operators 2 | 3 | | Operator | Arguments | Description | 4 | | -------- | -------------------- | ---------------------------- | 5 | | + | (Interval, Interval) | addition two intervals | 6 | | - | (Interval, Interval) | subtraction two intervals | 7 | | \* | (Interval, Interval) | multiplication two intervals | 8 | | / | (Interval, Interval) | division two intervals | 9 | 10 | 11 | | Name | Parameters | Return | Description | 12 | | ------------- | ---------- | -------- | --------------------------------------------------------- | 13 | | JUSTIFY_DAYS | Interval | Interval | Adjust interval, converting 30-day time periods to months | 14 | | JUSTIFY_HOURS | Interval | Interval | Adjust interval, converting 24-hour time periods to days | 15 | -------------------------------------------------------------------------------- /docs/functions/logical.md: -------------------------------------------------------------------------------- 1 | ### Logical Operators 2 | 3 | | Operator | Description | 4 | | -------- | ----------- | 5 | | AND | Logical AND | 6 | | && | Logical AND | 7 | | OR | Logical OR | 8 | | \|\| | Logical OR | 9 | | XOR | Logical XOR | -------------------------------------------------------------------------------- /docs/functions/math.md: -------------------------------------------------------------------------------- 1 | ### Mathematical Operators 2 | 3 | | Operator | Description | 4 | | -------- | ------------------- | 5 | | + | addition | 6 | | - | subtraction | 7 | | \* | multiplication | 8 | | / | division | 9 | | % | modulo | 10 | | ^ | exponentiation | 11 | | ~ | bitwise NOT | 12 | | & | bitwise AND | 13 | | \| | bitwise OR | 14 | | # | bitwise XOR | 15 | | >> | bitwise shift left | 16 | | << | bitwise shift right | 17 | 18 | --- 19 | 20 | ### Mathematical Functions 21 | 22 | | Name | Parameters | Return | Description | 23 | | ------ | ---------------- | ------- | ---------------------------------------------------------------------------- | 24 | | PI | | Float | Return the value of PI. | 25 | | FLOOR | Float | Integer | Returns the largest integer value that is smaller than or equal to a number. | 26 | | ROUND | Float, Integer? | Float | Returns a number rounded to a specified number of decimal places. | 27 | | SQUARE | Integer | Integer | Returns the square of an integer value. | 28 | | ABS | Number | Number | Returns the absolute value of an integer value. | 29 | | SIN | Float | Float | Returns the sine of a number. | 30 | | ASIN | Float | Float | Returns the arc sine of a number. | 31 | | COS | FLOAT | FLOAT | Returns the cosine of a number. | 32 | | ACOS | FLOAT | FLOAT | Returns the arc cosine of a number. | 33 | | TAN | FLOAT | FLOAT | Returns the tangent of a number. | 34 | | ATAN | FLOAT | FLOAT | Returns the arc tangent of a number. | 35 | | ATN2 | FLOAT, FLOAT | FLOAT | Returns the arc tangent of two values. | 36 | | SIGN | Number | Integer | Returns the sign of a number. | 37 | | MOD | Integer, Integer | Integer | Returns the remainder of a number divided by another number. | 38 | | RAND | Float? | Float | Returns a random number between 0 (inclusive) and 1 (exclusive). | 39 | -------------------------------------------------------------------------------- /docs/functions/other.md: -------------------------------------------------------------------------------- 1 | ### General functions 2 | 3 | | Name | Parameters | Return | Description | 4 | | --------- | ------------------- | ------- | ------------------------------------------------------------------------------ | 5 | | ISNULL | ANY | Boolean | Return TRUE if the argument type is null. | 6 | | ISNUMERIC | ANY | Boolean | Return TRUE if the argument type is number. | 7 | | TYPEOF | ANY | Text | Return the argument type name. | 8 | | GREATEST | ANY, Any, ...Any | Any | Return the greatest value from list of values | 9 | | LEAST | ANY, Any, ...Any | Any | Return the smallest value from list of values | 10 | | UUID | | Text | Return a Universal Unique Identifier | 11 | | IF | Boolean, T, T | T | Return second argument if the condition is TRUE otherwise return last argument | 12 | | IFNULL | T, T | T | Return second argument if first one is null, otherwise return first one | 13 | | BENCHMARK | Integer, Expression | Int(0) | Execute the expression n times and return 0 | 14 | -------------------------------------------------------------------------------- /docs/functions/range.md: -------------------------------------------------------------------------------- 1 | ### Range operators 2 | 3 | | Operator | Arguments | Description | 4 | | -------- | -------------------- | ----------- | 5 | | @> | (Range, T) | Contains | 6 | | <@ | (T, Range) | Contain by | 7 | | && | (Range, Range) | Overlap | 8 | 9 | ### Range functions 10 | 11 | | Name | Parameters | Return | Description | 12 | | --------- | ------------------ | --------------- | ---------------------------------------------------- | 13 | | INT4RANGE | Integer, Integer | Range(Integer) | Create a Range of integer type with start and end. | 14 | | DATERANGE | Date, Date | Range(Date) | Create a Range of date type with start and end. | 15 | | TSRANGE | DateTime, DateTime | Range(DateTime) | Create a Range of date time type with start and end. | 16 | | ISEMPTY | Range | Boolean | Return true of this range is empty. | -------------------------------------------------------------------------------- /docs/functions/regex.md: -------------------------------------------------------------------------------- 1 | ### Regex functions 2 | 3 | | Name | Parameters | Return | Description | 4 | | -------------- | ---------------- | ------- | ---------------------------------------------------------------------------------------- | 5 | | REGEXP_INSTR | Text, Text | Integer | Return starting index of substring matching regular expression. | 6 | | REGEXP_LIKE | Text, Text | Bool | Returns true if the string expr matches the regular expression specified by the pattern. | 7 | | REGEXP_REPLACE | Text, Text, Text | Text | Returns the input after replacing pattern with new content. | 8 | | REGEXP_SUBSTR | Text, Text | Text | Returns substring matching regular expression . | 9 | -------------------------------------------------------------------------------- /docs/functions/window.md: -------------------------------------------------------------------------------- 1 | ### Aggregations functions 2 | 3 | A Window function in GitQL performs a calculation on a window (frame) of values and returns a single value 4 | 5 | | Name | Parameters | Return | Description | 6 | | ----------- | ---------- | ------ | ---------------------------------------------------------------------------------- | 7 | | FIRST_VALUE | ANY | Any | Return first value in the window of values | 8 | | NTH_VALUE | ANY, INT | Any | Return n value in the window of values | 9 | | LAST_VALUE | ANY | Any | Return last value in the window of values | 10 | | ROW_NUMBER | | INT | Return unique sequential integer to each row within the partition, starting from 1 | 11 | -------------------------------------------------------------------------------- /docs/gitql_functions.md: -------------------------------------------------------------------------------- 1 | ## GitQL Application Functions 2 | 3 | Beside the common SQL scalar, aggregation and window functions there are some extra functions related to the GitQL as application not as SDK, 4 | those functions are available only in the gitql application. 5 | 6 | ### GitQL Commits functions 7 | 8 | | Name | Parameters | Return | Description | 9 | | ------------------- | ---------- | ------ | ------------------------------------------------------------------ | 10 | | COMMIT_CONVENTIONAL | Text | Text | Return the commit conventional from commits (Part before the `:`). | 11 | 12 | ### GitQL Diffs functions 13 | 14 | | Name | Parameters | Return | Description | 15 | | ---------------------------------- | ----------------- | ----------- | ------------------------------------------------------------------------ | 16 | | DIFF_CONTENT | DiffChanges | Text | Return the full content of all changes appended together. | 17 | | DIFF_ADDED_CONTENT | DiffChanges | Text | Return the added content of all changes appended together. | 18 | | DIFF_DELETED_CONTENT | DiffChanges | Text | Return the deleted content of all changes appended together. | 19 | | DIFF_MODIFIED_CONTENT | DiffChanges | Text | Return the modified content of all changes appended together. | 20 | | DIFF_CONTENT_CONTAINS | DiffChanges, Text | Text | Return true if the all content of changes contains second argument. | 21 | | DIFF_ADDED_CONTENT_CONTAINS | DiffChanges, Text | Text | Return true if the added content of changes contains second argument. | 22 | | DIFF_DELETED_CONTENT_CONTAINS | DiffChanges, Text | Text | Return true if the deleted content of changes contains second argument. | 23 | | DIFF_MODIFICATION_CONTENT_CONTAINS | DiffChanges, Text | Text | Return true if the modified content of changes contains second argument. | 24 | | DIFF_CHANGED_FILES | DiffChanges | Array | Return changes files in this change as array of strings. | 25 | | DIFF_FILES_COUNT | DiffChanges | Integer | Return number of unique files changes in this commit. | 26 | | IS_DIFF_HAS_FILE | DiffChanges, Text | Boolean | Return true if this diff changes contains file. | 27 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |

GQL - Git Query Language


2 | 3 |

4 | 5 |

6 | 7 |

8 | Crates.io 9 | Release 10 | Docs 11 | GitHub release 12 | GitHub issues 13 | GitHub 14 | GitHub all releases 15 |

16 | 17 | GQL is a query language with a syntax very similar to SQL with a tiny engine to perform queries on .git files instance of database files, the engine executes the query on the fly without the need to create database files or convert .git files into any other format, note that all Keywords in GQL are case-insensitive similar to SQL. 18 | 19 | ### Samples 20 | 21 | ``` sql 22 | SELECT 1 23 | SELECT 1 + 2 24 | SELECT LEN("Git Query Language") 25 | SELECT "One" IN ("One", "Two", "Three") 26 | SELECT "Git Query Language" LIKE "%Query%" 27 | SELECT INTERVAL '1 year 2 mons 3 days 04:05:06.789' 28 | 29 | SET @arr = [1, 2, 3]; 30 | SELECT [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; 31 | SELECT @arr[1], @arr[2], @arr[3], ARRAY_LENGTH(@arr); 32 | SELECT @arr[1:2], @arr[2:], @arr[:2]; 33 | 34 | SELECT DISTINCT title AS tt FROM commits 35 | SELECT author_name, COUNT(author_name) AS commit_num FROM commits GROUP BY author_name, author_email ORDER BY commit_num DESC LIMIT 10 36 | SELECT commit_count FROM branches WHERE commit_count BETWEEN 0 AND 10 37 | 38 | SELECT * FROM refs WHERE type = "branch" 39 | SELECT * FROM refs ORDER BY type 40 | 41 | SELECT * FROM commits 42 | SELECT author_name, author_email FROM commits 43 | SELECT author_name, author_email FROM commits ORDER BY author_name DESC, author_email ASC 44 | SELECT author_name, author_email FROM commits WHERE author_email LIKE "%gmail%" ORDER BY author_name 45 | SELECT * FROM commits WHERE LOWER(author_name) = "amrdeveloper" 46 | SELECT author_name FROM commits GROUP By author_name 47 | SELECT author_name FROM commits GROUP By author_name HAVING author_name = "AmrDeveloper" 48 | 49 | SELECT * FROM branches 50 | SELECT * FROM branches WHERE is_head = true 51 | SELECT name, LEN(name) FROM branches 52 | 53 | SELECT * FROM tags 54 | SELECT * FROM tags OFFSET 1 LIMIT 1 55 | 56 | SELECT path, count() AS changes_count, SUM(insertions) AS additions, SUM(removals) AS removes FROM diffs_changes GROUP BY path ORDER BY changes_count DESC 57 | ``` 58 | 59 | --- 60 | 61 | ## GitQL Documentation 62 | 63 | - [Full Documentation](https://amrdeveloper.github.io/GQL/) 64 | - [Install or Build](https://amrdeveloper.github.io/GQL/setup) 65 | - [Tables](https://amrdeveloper.github.io/GQL/structure/tables) 66 | - [Types](https://amrdeveloper.github.io/GQL/structure/types) 67 | - [Statements](https://amrdeveloper.github.io/GQL/statement) 68 | - [Expressions](https://amrdeveloper.github.io/GQL/expression) 69 | - [Functions and Operators](https://amrdeveloper.github.io/GQL/functions) 70 | - [Aggregations](https://amrdeveloper.github.io/GQL/functions/aggregations) 71 | 72 | ## GitQL SDK Documentations 73 | 74 | - [GitQL SDK](https://amrdeveloper.github.io/GQL/sdk/) 75 | - [Customize the Data Schema](https://amrdeveloper.github.io/GQL/sdk/schema.md). 76 | - [Customize the Data Provider](https://amrdeveloper.github.io/GQL/sdk/provider.md). 77 | - [Customize the Standard library](https://amrdeveloper.github.io/GQL/sdk/functions.md). 78 | - [Customize the Type system](https://amrdeveloper.github.io/GQL/sdk/types.md). 79 | - [Customize the Value system](https://amrdeveloper.github.io/GQL/sdk/values.md). 80 | - [Connect Components together](https://amrdeveloper.github.io/GQL/sdk/assemble.md). -------------------------------------------------------------------------------- /docs/sdk/assemble.md: -------------------------------------------------------------------------------- 1 | Now after we creating our `Schema`, `DataProvider`, may or may not add custom functions, types and values, it's time to make SDK components work together 2 | 3 | ```rust linenums="1" 4 | // Create instance of your Schema 5 | let schema = Schema { 6 | tables_fields_names: tables_fields_names().clone(), 7 | tables_fields_types: tables_fields_types().clone(), 8 | }; 9 | 10 | // Pass the standard functions, or your custom functions or mix of them to the env 11 | let std_signatures = standard_functions(); 12 | let std_functions = standard_function_signatures(); 13 | 14 | let aggregation_signatures = aggregation_function_signatures(); 15 | let aggregation_functions = aggregation_functions(); 16 | 17 | let mut env = Environment::new(schema); 18 | env.with_standard_functions(&std_signatures, std_functions); 19 | env.with_aggregation_functions(&aggregation_signatures, aggregation_functions); 20 | 21 | // Create instance of the diagnostic reporter, to report errors, warns ...etc 22 | let mut reporter = DiagnosticReporter::default(); 23 | 24 | // Pass the query to the tokenizer to get List of tokens or error 25 | let tokensOrError = tokenizer::tokenize(query.clone()); 26 | 27 | // If tokenizer return error, report it and stop 28 | if tokensOrError.is_err() { 29 | let diagnostic = tokensOrError.err().unwrap(); 30 | reporter.report_diagnostic(&query, *diagnostic); 31 | return; 32 | } 33 | 34 | // Get the list of tokens 35 | let tokens = tokensOrError.ok().unwrap(); 36 | 37 | // Start the parser to get AST or error 38 | let astOrError = parser::parse_gql(tokens, env); 39 | 40 | // Same like tokenizer if it return error, report it and stop 41 | if astOrError.is_err() { 42 | let diagnostic = astOrError.err().unwrap(); 43 | reporter.report_diagnostic(&query, *diagnostic); 44 | return; 45 | } 46 | 47 | let query_ast = astOrError.ok().unwrap(); 48 | 49 | // Create instance of your data provider 50 | let provider: Box = Box::new(FileDataProvider::new(repos.to_vec())); 51 | 52 | // Pass the ast and provider to the execution engine to get result or error 53 | let evaluation_result = engine::evaluate(env, &provider, query_node); 54 | 55 | // Report Runtime exceptions if they exists 56 | if evaluation_result.is_err() { 57 | reporter.report_diagnostic( 58 | &query, 59 | Diagnostic::exception(&evaluation_result.err().unwrap()), 60 | ); 61 | return; 62 | } 63 | 64 | let execution_result = evaluation_result.ok().unwrap(); 65 | 66 | // When you get result with selected groups, you can print them like table, json, csv or your custom format 67 | if let SelectedGroups(mut groups) = engine_result { 68 | let pagination = true; 69 | let page_size = 10; 70 | let printer = Box::new(TablePrinter::new(pagination, page_size)); 71 | printer.print(&mut groups); 72 | } 73 | ``` 74 | 75 | Thats it, now you can create a customizable query language with your own schema, data, types and functions. 76 | 77 | Enjoy. -------------------------------------------------------------------------------- /docs/sdk/functions.md: -------------------------------------------------------------------------------- 1 | By default the `gitql-std` component contains the standard functions and aggregations functions inspired by 2 | SQLite, MySQL, PostgreSQL, MSSQL ...etc. 3 | 4 | This can be more than enough in most cases, but maybe you want to create a special functions related to your data, 5 | or your custom type. 6 | 7 | ### Creating a custom function 8 | 9 | To create a new function you need to provide a name, signature (What are parameters types and return type) and the actual function implementation, the SDK expects a Map with type `HashMap<&'static str, Signature>` to map function name to the signature, and another map of type `&'static HashMap<&'static str, Function>` to map function name to the actual implementation. 10 | 11 | By default you can got those two maps with all standard functions like this 12 | 13 | ```rust linenums="1" 14 | let std_functions = standard_functions().to_owned(); 15 | let std_signatures = standard_function_signatures().to_owned(); 16 | ``` 17 | 18 | You can remove, replace or insert in those maps, lets take an example of adding new function 19 | 20 | Lets start by the function implementation, it should take an Array of Values as arguments, and return Value, 21 | so our function will take two parameters, file path and extension, and return true if this path end with this extension 22 | 23 | ```rust linenums="1" 24 | fn is_file_has_extension(values: &[Box]) -> Box { 25 | // Get the first argument 26 | let file_path = values[0].as_text().unwrap(); 27 | // Get the other argument 28 | let extension = values[1].as_text().unwrap(); 29 | // Check if path end with this extension 30 | let is_true = file_path.ends_with(&extension); 31 | // Return result 32 | Box::new(BoolValue { value : is_true }) 33 | } 34 | ``` 35 | 36 | 37 | After implementing our new function let append it to our clone of the standard functions 38 | 39 | ```rust linenums="1" 40 | // Append the function implementation 41 | let mut std_functions = standard_functions().to_owned(); 42 | std_functions.insert("is_file_has_extension", is_file_has_extension); 43 | 44 | // Append the function signature 45 | let mut std_signatures = standard_function_signatures().to_owned(); 46 | std_signatures.insert( 47 | "is_file_has_extension", 48 | Signature { 49 | // Take two Text values 50 | parameters: vec![Box::new(TextType), Box::new(TextType)], 51 | // Return Bool value 52 | return_type: Box::new(BoolType), 53 | } 54 | ); 55 | ``` 56 | 57 | --- 58 | 59 | > **_NOTE:_** You can remove functions, or even create a new empty map with only your functions. 60 | 61 | > **_NOTE:_** The same concepts works with Aggregations functions. 62 | 63 | > **_NOTE:_** Later you will see how to create function with your own types. -------------------------------------------------------------------------------- /docs/sdk/index.md: -------------------------------------------------------------------------------- 1 | The GitQL Query engine is designed to work as a set of decoupled libraries which gives you 2 | the flexibility to extend or replace any part of the query execution journey. 3 | 4 | ## GitQL Engine Architecture 5 | 6 | ``` mermaid 7 | graph LR 8 | A[SQL Query] --> B[Tokenizer]; 9 | B --> C[Parser and Type Checker]; 10 | C --> D[Execution Engine]; 11 | D --> E[Output] 12 | F[Schema] --> C; 13 | G[Data Provider] --> D; 14 | H[Standard library] --> D; 15 | ``` 16 | 17 | ## GitQL SDK Components 18 | 19 | | Component | Description | Install | 20 | | ------------ | :-------------------------------------------: | ---------------------------: | 21 | | gitql-core | Core components | `cargo install gitql-core` | 22 | | gitql-std | Standard library functions | `cargo install gitql-std` | 23 | | gitql-cli | CLI components like args parser, cli reporter | `cargo install gitql-cli` | 24 | | gitql-ast | structures components such as AST, Types | `cargo install gitql-ast` | 25 | | gitql-parser | Parser and Type checker components | `cargo install gitql-parser` | 26 | | gitql-engine | Execution engine component | `cargo install gitql-engine` | 27 | 28 | --- 29 | 30 | ## Using the GitQL SDk to extend the components 31 | 32 | As you will see building your own query language for specific need using the GitQL gives you the ability to customize every part of the engine such as operators, types, schema, functions ...etc. 33 | 34 | - [Customize the Data Schema](schema.md). 35 | - [Customize the Data Provider](provider.md). 36 | - [Customize the Standard library](functions.md). 37 | - [Customize the Type system](types.md). 38 | - [Customize the Value system](values.md). 39 | 40 | --- 41 | 42 | ## Example of product that build on top of GitQL 43 | 44 | - [ClangQL](https://github.com/AmrDeveloper/ClangQL): 45 | To run SQL query on C/C++ Code. 46 | 47 | - [FileQL](https://github.com/AmrDeveloper/FileQL): 48 | To run SQL query on the file system. 49 | 50 | - [LLQL](https://github.com/amrdeveloper/llql): 51 | Tool to run SQL query on LLVM IR/BC and perform Pattern matching on instructions. 52 | 53 | - [PyQL](https://github.com/AmrDeveloper/PyQL): 54 | To run SQL query on Python Code. 55 | 56 | Feel free to add your product too, everyone is welcome to join. 57 | -------------------------------------------------------------------------------- /docs/sdk/schema.md: -------------------------------------------------------------------------------- 1 | The GitQL parser and engine should be aware of what kind of data it should deal with, so it can provide 2 | a clean error messages, and apply the right operators, this information defined in a structure way in place called 3 | the `Schema`, and this schema contains 4 | 5 | - What tables you have. 6 | - What are the columns in each tables and what are their types. 7 | 8 | ```rust 9 | pub struct Schema { 10 | pub tables_fields_names: HashMap<&'static str, Vec<&'static str>>, 11 | pub tables_fields_types: HashMap<&'static str, Box>, 12 | } 13 | ``` 14 | 15 | So for your custom purpose you need to define your own schema, let take an example of a simple file system, 16 | so you have a table called `files`, and this table has two columns, `file_name` as Text (aka String), and `is_directory` as Boolean. 17 | 18 | ### Define the columns types 19 | 20 | ```rust linenums="1" 21 | pub fn tables_fields_types() -> HashMap<&'static str, Box> { 22 | let mut map: HashMap<&'static str, Box> = HashMap::new(); 23 | map.insert("file_name", Box::new(TextType)); 24 | map.insert("is_directory", Box::new(BoolType)); 25 | map 26 | } 27 | ``` 28 | 29 | ### Define the table name and his columns 30 | 31 | ```rust linenums="1" 32 | pub fn tables_fields_names() -> &'static HashMap<&'static str, Vec<&'static str>> { 33 | static HASHMAP: OnceLock>> = OnceLock::new(); 34 | HASHMAP.get_or_init(|| { 35 | let mut map = HashMap::new(); 36 | map.insert("files", vec!["file_name", "is_directory"]); 37 | map 38 | ); 39 | } 40 | ``` 41 | 42 | ### Create a schema object with those information 43 | 44 | ```rust linenums="1" 45 | let schema = Schema { 46 | tables_fields_names: tables_fields_names().to_owned(), 47 | tables_fields_types: tables_fields_types().to_owned(), 48 | }; 49 | ``` 50 | 51 | Later this schema instance with the standard library will used to create the environment 52 | 53 | ```rust linenums="1" 54 | let mut env = Environment::new(schema); 55 | ``` -------------------------------------------------------------------------------- /docs/sdk/types.md: -------------------------------------------------------------------------------- 1 | Now after creating our own `Schema`, `DataProvider` and `Standard function`, most of the cases can be handled 2 | at this level, but what if you want to store the values in other type than `Int`, `Float`, `Text` ...etc, 3 | for example you want to create a type called `Song` or `Video` and then you can run a query asking 4 | what is the longest video in each directory? or what if you want to create a function that take a TreeNode and return number children nodes? 5 | 6 | --- 7 | 8 | To allow this level of customization in type system we need to make it easy to integrate with the parser, type checker 9 | and execution engine? we can't support every possible type in the engine. 10 | 11 | But .... What if we support 0 types 🤔 .... what if the types are not primitives in the engine but defined in the Standard library or the SDK? 12 | 13 | --- 14 | 15 | ### Moving the Types from the Engine to the SDK level 16 | 17 | This idea is inspired by `Chris Lattner` design in Mojo and Swift programming languages, 18 | by default the Engine has some built in predefined types like `Integer`, `Float`, `Boolean` ...etc, it know very well 19 | what operators can work with them, how can cast them, this can work well and programming languages gives you the ability to 20 | compose types and create Structured data types like `List`, `File`, `GamePlayer` ...etc, but what if we create a way to define your own type, 21 | and define how the Engine or the Compiler can deal with it, define what operators work with it, in this case we can define `Integer`, `Float` 22 | as part of the Standard library or in our case the SDK, and that also gives the SDK user the power to built any type he want to make it integrated 23 | well in all part of the Query Engine. 24 | 25 | Lets for example say we want to create type called `IntPair` and define attributes and operators for it, 26 | 27 | ### Creating a custom DataType 28 | 29 | ```rust linenums="1" 30 | use std::any::Any; 31 | 32 | use super::base::DataType; 33 | 34 | #[derive(Clone)] 35 | pub struct IntPairType; 36 | 37 | impl DataType for IntPairType { 38 | 39 | /// Define the literal representation for our new type 40 | fn literal(&self) -> String { 41 | "IntPair".to_string() 42 | } 43 | 44 | /// Define how to compare this type with others 45 | fn equals(&self, other: &Box) -> bool { 46 | let int_pair_type: Box = Box::new(self.clone()); 47 | other.is_any() || other.is_int() || other.is_variant_contains(&int_pair_type) 48 | } 49 | 50 | fn as_any(&self) -> &dyn Any { 51 | self 52 | } 53 | 54 | /// Allow using the `+` operator between two IntPair's 55 | fn can_perform_add_op_with(&self) -> Vec> { 56 | vec![Box::new(IntPairType)] 57 | } 58 | 59 | /// Define that the result of IntPair + IntPair will be another IntPair 60 | fn add_op_result_type(&self, _other: &Box) -> Box { 61 | Box::new(IntPairType) 62 | } 63 | 64 | /// Define any other operators like -, *, %, >, ...etc 65 | } 66 | ``` 67 | 68 | Now if we create a new Function with Signature that accept IntPair and we pass Int, it will report an error, but now we created a Type but to create a Value with this type we need to create a Custom Value too ([Creating the IntPairValue as Custom value](values.md)). 69 | 70 | ### Register your type in TypesTable 71 | 72 | If you want to support using your type in explicit casting feature, for example 73 | 74 | ```SQL 75 | SELECT CAST(value AS IntPairType) 76 | ``` 77 | 78 | You must register your type to the TypesTable component and pass it to the `Environment` for example 79 | 80 | ```rust 81 | let types_table = TypesTable::new(); 82 | types_table.register("intpair", Box::new(IntPairType)); 83 | 84 | let env = Environment::new(); 85 | env.register_types_table(types_table); 86 | ``` -------------------------------------------------------------------------------- /docs/sdk/values.md: -------------------------------------------------------------------------------- 1 | Now we created a new Type called `IntPairType` that can be used as Function parameter, return type or column type, but we need a custom value that can represent this type, it's almost the same concept, so lets start creating the `IntPairValue`. 2 | 3 | ```rust linenums="1" 4 | use gitql_ast::types::DataType; 5 | 6 | use super::base::Value; 7 | 8 | #[derive(Clone)] 9 | pub struct IntPairValue { 10 | pub first: i64, 11 | pub second: i64, 12 | } 13 | 14 | impl Value for IntPairValue { 15 | 16 | /// Define the literal representation for our new Value 17 | fn literal(&self) -> String { 18 | format!("({}, {})", self.first, self.second) 19 | } 20 | 21 | /// Define how to check equality between this value and other 22 | fn equals(&self, other: &Box) -> bool { 23 | if let Some(other_int_pair) = other.as_any().downcast_ref::() { 24 | return self.first == other_int_pair.first 25 | && self.second == other_int_pair.second; 26 | } 27 | false 28 | } 29 | 30 | /// You can define how to order between IntPair values or None to disable ordering 31 | fn compare(&self, other: &Box) -> Option { 32 | None 33 | } 34 | 35 | fn data_type(&self) -> Box { 36 | Box::new(IntPairType) 37 | } 38 | 39 | fn as_any(&self) -> &dyn Any { 40 | self 41 | } 42 | 43 | /// As we allowed `+` between IntPair types in `can_perform_add_op_with` 44 | /// We need also to define how this operator will work 45 | fn add_op(&self, other: &Box) -> Result, String> { 46 | if let Some(other_int) = other.as_any().downcast_ref::() { 47 | let first = self.first + other_int.first; 48 | let second = self.second + other_int.second; 49 | return Ok(Box::new(IntPairValue { first, second })); 50 | } 51 | 52 | /// Write your exception message 53 | Err("Unexpected type to perform `+` with".to_string()) 54 | } 55 | } 56 | ``` 57 | 58 | --- 59 | 60 | ### Creating Function to construct IntPairValue 61 | 62 | ```rust linenums="1" 63 | fn new_int_pair(values: &[Box]) -> Box { 64 | let first = values[0].as_int().unwrap(); 65 | let second = values[1].as_int().unwrap(); 66 | Ok(Box::new(IntPairValue { first, second })) 67 | } 68 | ``` 69 | 70 | ### Register this function signature and implementation 71 | 72 | ```rust linenums="1" 73 | // Append the function implementation 74 | let mut std_functions = standard_functions().to_owned(); 75 | std_functions.insert("new_int_pair", new_int_pair); 76 | 77 | // Append the function signature 78 | let mut std_signatures = standard_function_signatures().to_owned(); 79 | std_signatures.insert( 80 | "new_int_pair", 81 | Signature { 82 | // Take two Integers values 83 | parameters: vec![Box::new(IntType), Box::new(IntType)], 84 | // Return IntPair Value 85 | return_type: Box::new(IntPairValue), 86 | } 87 | ); 88 | ``` 89 | 90 | After connecting everything together in the next step, you can perform query like this 91 | 92 | ```sql 93 | SELECT new_int_pair(1, 2) + new_int_pair(3, 4); 94 | ``` 95 | 96 | And got result like `(4, 6)`. 97 | 98 | ### Going forward 99 | 100 | This is just a quick example of how to create your own types, but you can create any type you want, even Data structures like Map and allow 101 | index operator for it so you can write 102 | 103 | ```sql 104 | SELECT map["key"] 105 | ``` -------------------------------------------------------------------------------- /docs/setup.md: -------------------------------------------------------------------------------- 1 | ## Install from Package managers 2 | 3 | ## Cargo.io 4 | 5 | ```sh 6 | cargo install gitql 7 | ``` 8 | 9 | > Note that from version `0.10.0` onward installing from Cargo requires `Cmake` to be installed so it can build the dependencies. 10 | 11 | ## Winget on Windows 12 | 13 | ```sh 14 | winget install gitql 15 | ``` 16 | 17 | ## Scoop on Windows 18 | 19 | ```sh 20 | scoop install gitql 21 | ``` 22 | 23 | ## Homebrew on MacOS and Linux 24 | 25 | ```sh 26 | brew install gql 27 | ``` 28 | 29 | # On Single repository 30 | gitql 31 | 32 | # On multi repositories 33 | gitql --repo ...etc 34 | 35 | # Or 36 | gitql -r ...etc 37 | ``` 38 | 39 | ## Download Binaries 40 | 41 | From Github repository page you can download the right executable for your OS and Arch from the latest release 42 | 43 | ## Build GQL From source code 44 | 45 | ```sh 46 | git clone https://github.com/amrdeveloper/gql 47 | cd gql 48 | 49 | # On Single repository 50 | cargo run 51 | 52 | # On multi repositories 53 | cargo run -- --repo ...etc 54 | cargo run -- -r ...etc 55 | ``` 56 | 57 | # Command line arguments 58 | 59 | ``` 60 | Usage: gitql [OPTIONS] 61 | 62 | Options: 63 | -r, --repos Path for local repositories to run query on 64 | -s, --script Script file contains one or more query" 65 | -q, --query GitQL query to run on selected repositories 66 | -p, --pagination Enable print result with pagination 67 | -ps, --pagesize Set pagination page size [default: 10] 68 | -o, --output Set output format [render, json, csv] 69 | -a, --analysis Print Query analysis 70 | -e, --editor Enable GitQL LineEditor 71 | -h, --help Print GitQL help 72 | -v, --version Print GitQL Current Version 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/statement/do.md: -------------------------------------------------------------------------------- 1 | The `Do` works in a similar way to the SELECT statement, but without returning a result set 2 | 3 | For example to select all fields from commits table. 4 | 5 | ```sql 6 | DO SLEEP(10); 7 | ``` 8 | 9 | Where DO Cannot be Used 10 | 11 | We can’t use DO everywhere that we can use SELECT. For example we can’t do the following: 12 | 13 | ```sql 14 | DO * FROM branches; 15 | ``` -------------------------------------------------------------------------------- /docs/statement/group_by.md: -------------------------------------------------------------------------------- 1 | ### Group By Statement 2 | 3 | The `GROUP BY` statement groups rows that have the same values into summary rows, like "find the number of commits for each username or email". 4 | 5 | ```SQL 6 | SELECT * FROM commits GROUP BY author_name 7 | SELECT * FROM commits GROUP BY author_name, author_email 8 | SELECT * FROM commits GROUP BY LEN(author_name) 9 | ``` 10 | 11 | You can use The ROLLUP feature to extends GROUP BY to include subtotals and grand totals in the result set. 12 | 13 | ```SQL 14 | SELECT COUNT() FROM commits GROUP BY author_name WITH ROLLUP 15 | ``` -------------------------------------------------------------------------------- /docs/statement/having.md: -------------------------------------------------------------------------------- 1 | The `HAVING` statement is very similar to `WHERE` expect that it evaluated after the `GROUP BY` statement 2 | 3 | ```sql 4 | SELECT * FROM commits GROUP BY author_name HAVING author_name = "AmrDeveloper" 5 | SELECT * FROM branches GROUP BY name HAVING is_head = "true" 6 | ``` -------------------------------------------------------------------------------- /docs/statement/index.md: -------------------------------------------------------------------------------- 1 | GitQL Statements 2 | 3 | - [Variables Statement](variables.md). 4 | - [Select Statement](select.md). 5 | - [Do Statement](do.md). 6 | - [Where Statement](where.md). 7 | - [Having Statement](having.md). 8 | - [Order by Statement](order_by.md). 9 | - [Group by Statement](group_by.md). 10 | - [Limit and Offset Statements](limit_and_offset.md). 11 | -------------------------------------------------------------------------------- /docs/statement/limit_and_offset.md: -------------------------------------------------------------------------------- 1 | The `LIMIT` statement used to limit the number of end result 2 | 3 | ```sql 4 | SELECT * FROM commits LIMIT 10 5 | SELECT * FROM branches LIMIT 15 6 | ``` 7 | 8 | The `OFFSET` statement specifies how many rows to skip at the beginning of the result set 9 | 10 | ```sql 11 | SELECT * FROM commits OFFSET 10 12 | SELECT * FROM branches OFFSET 15 13 | ``` 14 | 15 | You can mix the offset and limit statements 16 | 17 | ```sql 18 | SELECT * FROM commits OFFSET 10 LIMIT 10 19 | SELECT * FROM branches OFFSET 15 LIMIT 15 20 | ``` -------------------------------------------------------------------------------- /docs/statement/order_by.md: -------------------------------------------------------------------------------- 1 | The `ORDER BY` Statement used to order the result-set in ascending or descending order by one or more arguments. 2 | 3 | ```sql 4 | SELECT author_name, author_email FROM commits ORDER BY author_name 5 | SELECT author_name, author_email FROM commits ORDER BY author_name, author_email 6 | SELECT author_name, author_email FROM commits ORDER BY author_email, commit_id ASC 7 | SELECT author_name, author_email FROM commits ORDER BY author_name DESC 8 | SELECT author_name, author_email FROM commits ORDER BY author_name, LEN(author_name) 9 | ``` 10 | 11 | The `ORDER BY` Statement with `USING ` syntax inspired by PostgreSQL 12 | 13 | ```sql 14 | SELECT author_name, author_email FROM commits ORDER BY author_email, commit_id USING < 15 | SELECT author_name, author_email FROM commits ORDER BY author_name USING > 16 | ``` 17 | 18 | You can define nulls order policy to set if you want null value to be first or last in the order 19 | 20 | ```sql 21 | SELECT author_name, author_email FROM commits ORDER BY author_email NULLS FIRST 22 | SELECT author_name, author_email FROM commits ORDER BY author_name NULLS LAST 23 | ``` -------------------------------------------------------------------------------- /docs/statement/qualify.md: -------------------------------------------------------------------------------- 1 | The `QUALIFY` statement is very similar to `WHERE` expect that it evaluated after the `WINDOW functions` -------------------------------------------------------------------------------- /docs/statement/select.md: -------------------------------------------------------------------------------- 1 | ### Select Statement 2 | 3 | The `SELECT` statement is used to query data from a single table 4 | 5 | For example to select all fields from commits table. 6 | 7 | ```sql 8 | SELECT * FROM commits 9 | ``` 10 | 11 | Or Selecting just title and message 12 | 13 | ```sql 14 | SELECT title message FROM commits 15 | ``` 16 | 17 | You can use Aggregation function in the select statement to perform function on all data until the current one 18 | 19 | ```sql 20 | SELECT count(author_name) FROM commits 21 | ``` 22 | 23 | You can alias the column name only in this query by using `AS` keyword for example 24 | 25 | ```sql 26 | SELECT title as commit_title FROM commits 27 | SELECT title as "Commit Title" FROM commits 28 | SELECT name, commit_count, max(commit_count) AS max_count message FROM branches 29 | ``` 30 | 31 | --- 32 | 33 | ### Distinct option 34 | 35 | You can select unique rows only using the `distinct` keyword for example, 36 | 37 | ```sql 38 | SELECT DISTINCT title AS tt FROM commits 39 | ``` 40 | 41 | --- 42 | 43 | ### Distinct On option 44 | 45 | You can select rows with unique fields using the `distinct on` keyword with one or more field for example, 46 | 47 | ```sql 48 | SELECT DISTINCT ON (author_name) title AS tt FROM commits 49 | ``` 50 | 51 | ### Joins 52 | 53 | You can perform one or more JOIN to join two tables together, you can use one of four different join types, 54 | which are Inner, Cross, Left and Right outer JOINS and also filter by on predicate condition. 55 | 56 | ```sql 57 | SELECT COUNT() FROM tags JOIN branches 58 | SELECT COUNT() FROM tags LEFT JOIN branches ON commit_count > 1 59 | SELECT COUNT() FROM tags RIGHT JOIN branches ON commit_count > 1 60 | ``` 61 | 62 | ### Select ... INTO 63 | 64 | You can export the query result into external file using the syntax `INTO OUTFILE ` 65 | 66 | ```sql 67 | SELECT name FROM branches INTO OUTFILE "branches.txt" 68 | ``` 69 | 70 | You can format the output result with options for example 71 | 72 | ```sql 73 | SELECT * FROM branches INTO OUTFILE "branches.txt" FIELDS TERMINATED BY "," LINES TERMINATED BY "\n" ENCLOSED "|" 74 | ``` 75 | 76 | If you want to just dump the data without any format you can use `INTO DUMPFILE` 77 | 78 | ```sql 79 | SELECT * FROM branches INTO DUMPFILE "braches.txt" 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/statement/variables.md: -------------------------------------------------------------------------------- 1 | GitQL has support for global variables with syntax inspired by MySQL 2 | 3 | ### Declare variable with value 4 | 5 | ```sql 6 | SET @one = 1 7 | SET @STRING = "GitQL" 8 | ``` 9 | 10 | ### Use the variable 11 | You can use the variable like any other symbol using the name 12 | 13 | ```sql 14 | SELECT @one 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/statement/where.md: -------------------------------------------------------------------------------- 1 | The `WHERE` statement is used to filter the data by one or more conditions 2 | 3 | For example to select all commits for a specific username 4 | 5 | ```sql 6 | SELECT * FROM commits where author_name = "AmrDeveloper" 7 | SELECT * FROM branches WHERE is_head = "true" 8 | ``` 9 | 10 | You can add Unary and Binary expressions, but you can use Aggregation functions inside the Where statement, because it calculated after the group by statement. -------------------------------------------------------------------------------- /docs/structure/types.md: -------------------------------------------------------------------------------- 1 | The GQL has a basic type system with only four types to be used for safe functions call and expressions. 2 | 3 | ### Types information's 4 | | Name | Description | 5 | | -------------- | -------------------------------------------- | 6 | | Any | Used to represent Any data type | 7 | | Text | Used to represent string literal | 8 | | Integer | Used to represent integers | 9 | | Float | Used to represent floats | 10 | | Boolean | Used to represent boolean value | 11 | | Date | Used to represent date | 12 | | Time | Used to represent time | 13 | | DateTime | Used to represent date & time | 14 | | Array(type) | Used to represent an array of type | 15 | | Range(type) | Used to represent a range of type | 16 | | Variant(types) | Used to represent a variant of types | 17 | | Optional(type) | Used to represent a optional type or none | 18 | | Varargs(type) | Used to represent a variable arguments type | 19 | | Composite | Used to represent a composite of other types | -------------------------------------------------------------------------------- /media/gitql_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmrDeveloper/GQL/07f811b57241d5265d55f11cc6f2615a98603216/media/gitql_demo.gif -------------------------------------------------------------------------------- /media/gitql_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /scripts/cargo-out-dir: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Finds Cargo's `OUT_DIR` directory from the most recent build. 4 | # 5 | # This requires one parameter corresponding to the target directory 6 | # to search for the build output. 7 | 8 | if [ $# != 1 ]; then 9 | echo "Usage: $(basename "$0") " >&2 10 | exit 2 11 | fi 12 | 13 | # This works by finding the most recent stamp file, which is produced by 14 | # every ripgrep build. 15 | target_dir="$1" 16 | find "$target_dir" -name ripgrep-stamp -print0 \ 17 | | xargs -0 ls -t \ 18 | | head -n1 \ 19 | | xargs dirname -------------------------------------------------------------------------------- /scripts/sha256.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import hashlib 3 | 4 | h = hashlib.sha256() 5 | 6 | with open(sys.argv[1], "rb") as f: 7 | while True: 8 | chunk = f.read(4096) 9 | if not chunk: 10 | break 11 | h.update(chunk) 12 | 13 | print(h.hexdigest()) -------------------------------------------------------------------------------- /scripts/summarize-release.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | version = sys.argv[1] 6 | base = sys.argv[2] 7 | 8 | checksums = {} 9 | 10 | for folder in os.listdir(base): 11 | for filename in os.listdir(os.path.join(base, folder)): 12 | if filename.endswith(".sha256"): 13 | with open(os.path.join(base, folder, filename)) as f: 14 | sha256 = f.read().strip() 15 | checksums[filename[:-7]] = sha256 16 | 17 | print(json.dumps({ 18 | "version": version, 19 | "checksums": checksums, 20 | }, indent=2)) -------------------------------------------------------------------------------- /src/gitql/functions/commits.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use gitql_ast::types::text::TextType; 4 | use gitql_core::signature::Signature; 5 | use gitql_core::signature::StandardFunction; 6 | use gitql_core::values::text::TextValue; 7 | use gitql_core::values::Value; 8 | 9 | #[inline(always)] 10 | pub(crate) fn register_commits_functions(map: &mut HashMap<&'static str, StandardFunction>) { 11 | map.insert("commit_conventional", commit_conventional); 12 | } 13 | 14 | #[inline(always)] 15 | pub(crate) fn register_commits_function_signatures(map: &mut HashMap<&'static str, Signature>) { 16 | map.insert( 17 | "commit_conventional", 18 | Signature::with_return(Box::new(TextType)).add_parameter(Box::new(TextType)), 19 | ); 20 | } 21 | 22 | fn commit_conventional(values: &[Box]) -> Box { 23 | let text = values[0].as_text().unwrap(); 24 | let split: Vec<&str> = text.split(':').collect(); 25 | let value = if split.len() == 1 { "" } else { split[0] }.to_string(); 26 | Box::new(TextValue::new(value)) 27 | } 28 | -------------------------------------------------------------------------------- /src/gitql/functions/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::OnceLock; 3 | 4 | use commits::register_commits_function_signatures; 5 | use commits::register_commits_functions; 6 | use diffs::register_diffs_function_signatures; 7 | use diffs::register_diffs_functions; 8 | use gitql_core::signature::Signature; 9 | use gitql_core::signature::StandardFunction; 10 | use gitql_std::standard::standard_function_signatures; 11 | use gitql_std::standard::standard_functions; 12 | 13 | mod commits; 14 | mod diffs; 15 | 16 | pub fn gitql_std_functions() -> &'static HashMap<&'static str, StandardFunction> { 17 | static HASHMAP: OnceLock> = OnceLock::new(); 18 | HASHMAP.get_or_init(|| { 19 | let mut map = standard_functions().to_owned(); 20 | register_commits_functions(&mut map); 21 | register_diffs_functions(&mut map); 22 | map 23 | }) 24 | } 25 | 26 | pub fn gitql_std_signatures() -> HashMap<&'static str, Signature> { 27 | let mut map = standard_function_signatures().to_owned(); 28 | register_commits_function_signatures(&mut map); 29 | register_diffs_function_signatures(&mut map); 30 | map 31 | } 32 | -------------------------------------------------------------------------------- /src/gitql/gitql_schema.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::OnceLock; 3 | 4 | use gitql_ast::types::boolean::BoolType; 5 | use gitql_ast::types::datetime::DateTimeType; 6 | use gitql_ast::types::integer::IntType; 7 | use gitql_ast::types::text::TextType; 8 | use gitql_ast::types::DataType; 9 | 10 | use crate::gitql::types::diff_changes::DiffChangesType; 11 | 12 | pub fn tables_fields_types() -> HashMap<&'static str, Box> { 13 | let mut map: HashMap<&'static str, Box> = HashMap::new(); 14 | map.insert("commit_id", Box::new(TextType)); 15 | map.insert("title", Box::new(TextType)); 16 | map.insert("message", Box::new(TextType)); 17 | map.insert("name", Box::new(TextType)); 18 | map.insert("author_name", Box::new(TextType)); 19 | map.insert("author_email", Box::new(TextType)); 20 | map.insert("committer_name", Box::new(TextType)); 21 | map.insert("committer_email", Box::new(TextType)); 22 | map.insert("full_name", Box::new(TextType)); 23 | map.insert("insertions", Box::new(IntType)); 24 | map.insert("removals", Box::new(IntType)); 25 | map.insert("diff_changes", Box::new(DiffChangesType)); 26 | map.insert("files_changed", Box::new(IntType)); 27 | map.insert("type", Box::new(TextType)); 28 | map.insert("datetime", Box::new(DateTimeType)); 29 | map.insert("is_head", Box::new(BoolType)); 30 | map.insert("is_remote", Box::new(BoolType)); 31 | map.insert("commit_count", Box::new(IntType)); 32 | map.insert("parents_count", Box::new(IntType)); 33 | map.insert("updated", Box::new(DateTimeType)); 34 | map.insert("path", Box::new(TextType)); 35 | map.insert("mode", Box::new(TextType)); 36 | map.insert("repo", Box::new(TextType)); 37 | map 38 | } 39 | 40 | pub fn tables_fields_names() -> &'static HashMap<&'static str, Vec<&'static str>> { 41 | static HASHMAP: OnceLock>> = OnceLock::new(); 42 | HASHMAP.get_or_init(|| { 43 | let mut map = HashMap::new(); 44 | map.insert("refs", vec!["name", "full_name", "type", "repo"]); 45 | map.insert( 46 | "commits", 47 | vec![ 48 | "commit_id", 49 | "title", 50 | "message", 51 | "author_name", 52 | "author_email", 53 | "committer_name", 54 | "committer_email", 55 | "datetime", 56 | "parents_count", 57 | "repo", 58 | ], 59 | ); 60 | map.insert( 61 | "branches", 62 | vec![ 63 | "name", 64 | "commit_count", 65 | "is_head", 66 | "is_remote", 67 | "updated", 68 | "repo", 69 | ], 70 | ); 71 | map.insert( 72 | "diffs", 73 | vec![ 74 | "commit_id", 75 | "author_name", 76 | "author_email", 77 | "insertions", 78 | "removals", 79 | "files_changed", 80 | "diff_changes", 81 | "datetime", 82 | "repo", 83 | ], 84 | ); 85 | map.insert( 86 | "diffs_changes", 87 | vec![ 88 | "commit_id", 89 | "insertions", 90 | "removals", 91 | "mode", 92 | "path", 93 | "repo", 94 | ], 95 | ); 96 | map.insert("tags", vec!["name", "repo"]); 97 | map 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /src/gitql/mod.rs: -------------------------------------------------------------------------------- 1 | use gitql_core::environment::Environment; 2 | use gitql_core::schema::Schema; 3 | use gitql_schema::tables_fields_names; 4 | use gitql_schema::tables_fields_types; 5 | use gitql_std::aggregation::aggregation_function_signatures; 6 | use gitql_std::aggregation::aggregation_functions; 7 | use gitql_std::window::window_function_signatures; 8 | use gitql_std::window::window_functions; 9 | 10 | pub(crate) mod functions; 11 | pub(crate) mod gitql_data_provider; 12 | pub(crate) mod gitql_line_editor; 13 | pub(crate) mod gitql_schema; 14 | pub(crate) mod types; 15 | pub(crate) mod values; 16 | 17 | pub(crate) fn create_gitql_environment() -> Environment { 18 | let schema = Schema { 19 | tables_fields_names: tables_fields_names().to_owned(), 20 | tables_fields_types: tables_fields_types().to_owned(), 21 | }; 22 | 23 | let std_signatures = functions::gitql_std_signatures(); 24 | let std_functions = functions::gitql_std_functions(); 25 | 26 | let aggregation_signatures = aggregation_function_signatures(); 27 | let aggregation_functions = aggregation_functions(); 28 | 29 | let window_signatures = window_function_signatures(); 30 | let window_function = window_functions(); 31 | 32 | let mut env = Environment::new(schema); 33 | env.with_standard_functions(&std_signatures, std_functions); 34 | env.with_aggregation_functions(&aggregation_signatures, aggregation_functions); 35 | env.with_window_functions(&window_signatures, window_function); 36 | env 37 | } 38 | 39 | pub(crate) fn validate_git_repositories( 40 | repositories: &Vec, 41 | ) -> Result, String> { 42 | let mut git_repositories: Vec = vec![]; 43 | for repository in repositories { 44 | let git_repository = gix::open(repository); 45 | if git_repository.is_err() { 46 | return Err(git_repository.err().unwrap().to_string()); 47 | } 48 | git_repositories.push(git_repository.ok().unwrap()); 49 | } 50 | Ok(git_repositories) 51 | } 52 | -------------------------------------------------------------------------------- /src/gitql/types/diff_changes.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use gitql_ast::types::DataType; 4 | 5 | #[derive(Clone)] 6 | pub struct DiffChangesType; 7 | 8 | impl DataType for DiffChangesType { 9 | fn literal(&self) -> String { 10 | "DiffChangesType".to_owned() 11 | } 12 | 13 | #[allow(clippy::borrowed_box)] 14 | fn equals(&self, other: &Box) -> bool { 15 | let self_type: Box = Box::new(DiffChangesType); 16 | other.is_any() 17 | || other.is_variant_contains(&self_type) 18 | || other.as_any().downcast_ref::().is_some() 19 | } 20 | 21 | fn as_any(&self) -> &dyn Any { 22 | self 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gitql/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod diff_changes; 2 | -------------------------------------------------------------------------------- /src/gitql/values/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod diff_changes; 2 | --------------------------------------------------------------------------------