├── .editorconfig
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ ├── feature_request.yml
│ └── support_request.yml
└── workflows
│ ├── checklist_validator.yml
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .sync
├── APP_VERSION
├── CHANGELOG.md
├── Hello
├── LICENSE
├── PHP_VERSION
├── README.md
├── bin
├── cluster
├── codeanalyze
├── codestyle
├── codestyle-fix
├── git-changelog
├── query
└── test
├── composer.json
├── composer.lock
├── docker-compose-dev.yml
├── docker-compose.yml
├── packages
└── rpm.spec
├── phpstan.neon
├── phpunit.xml
├── plugins
└── .gitkeep
├── ruleset.xml
├── src
├── Config
│ └── LogLevel.php
├── Exception
│ ├── SQLQueryCommandMissing.php
│ └── SQLQueryCommandNotSupported.php
├── Handler.php
├── Lib
│ ├── CliArgsProcessor.php
│ ├── CrashDetector.php
│ ├── Metric.php
│ ├── MetricThread.php
│ └── QueryProcessor.php
├── Network
│ ├── EventHandler.php
│ └── Server.php
├── Plugin
│ ├── Alias
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── AlterColumn
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── AlterDistributedTable
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── AlterRenameTable
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Autocomplete
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Backup
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── CliTable
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── CreateCluster
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── CreateTable
│ │ ├── Handler.php
│ │ ├── Payload.php
│ │ └── WithEngineHandler.php
│ ├── DistributedInsert
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Drop
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── EmptyString
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── EmulateElastic
│ │ ├── AddAliasHandler.php
│ │ ├── AddEntityHandler.php
│ │ ├── AddTemplateHandler.php
│ │ ├── BaseEntityHandler.php
│ │ ├── CatHandler.php
│ │ ├── ClusterKibanaHandler.php
│ │ ├── CountInfoKibanaHandler.php
│ │ ├── CreateTableHandler.php
│ │ ├── FieldCapsHandler.php
│ │ ├── FieldCapsHandlerHelper.php
│ │ ├── FindEntityHandler.php
│ │ ├── GetAliasesHandler.php
│ │ ├── GetEntityHandler.php
│ │ ├── ImportKibanaHandler.php
│ │ ├── InitKibanaHandler.php
│ │ ├── InvalidSourceKibanaHandler.php
│ │ ├── KibanaSearch
│ │ │ ├── Handler.php
│ │ │ ├── Logic
│ │ │ │ ├── Request
│ │ │ │ │ ├── Aliasing.php
│ │ │ │ │ ├── Executor.php
│ │ │ │ │ ├── Factory.php
│ │ │ │ │ ├── FieldDetecting.php
│ │ │ │ │ ├── Filtering.php
│ │ │ │ │ ├── Interfaces
│ │ │ │ │ │ ├── FailableLogicInterface.php
│ │ │ │ │ │ └── RequestLogicInterface.php
│ │ │ │ │ └── Ordering.php
│ │ │ │ └── Response
│ │ │ │ │ ├── BaseLogic.php
│ │ │ │ │ ├── ConcurrentFilterProcessing
│ │ │ │ │ ├── FilterSet.php
│ │ │ │ │ └── Processing.php
│ │ │ │ │ ├── DisabledMetricAdding.php
│ │ │ │ │ ├── Executor.php
│ │ │ │ │ ├── Factory.php
│ │ │ │ │ ├── HistogramExtending.php
│ │ │ │ │ ├── Interfaces
│ │ │ │ │ └── ResponseLogicInterface.php
│ │ │ │ │ ├── Sorting
│ │ │ │ │ ├── Metric
│ │ │ │ │ │ ├── Calculator.php
│ │ │ │ │ │ ├── CalculatorFactory.php
│ │ │ │ │ │ ├── CountCalculator.php
│ │ │ │ │ │ ├── MetricCalculatorInterface.php
│ │ │ │ │ │ ├── MetricUpdaterInterface.php
│ │ │ │ │ │ └── Operation.php
│ │ │ │ │ ├── SortField.php
│ │ │ │ │ └── Sorting.php
│ │ │ │ │ └── UnmatchedFilterProcessing.php
│ │ │ ├── NodeSet.php
│ │ │ ├── RequestNode
│ │ │ │ ├── AggNode.php
│ │ │ │ ├── BaseNode.php
│ │ │ │ ├── BaseRange.php
│ │ │ │ ├── DateHistogram.php
│ │ │ │ ├── DateRange.php
│ │ │ │ ├── ExprNode.php
│ │ │ │ ├── Factory.php
│ │ │ │ ├── GroupExprNode.php
│ │ │ │ ├── GroupFilter.php
│ │ │ │ ├── Helpers
│ │ │ │ │ ├── FilterExpression
│ │ │ │ │ │ ├── Factory.php
│ │ │ │ │ │ └── FilterExpression.php
│ │ │ │ │ └── TimeZoneExpression.php
│ │ │ │ ├── Histogram.php
│ │ │ │ ├── Interfaces
│ │ │ │ │ ├── AggNodeInterface.php
│ │ │ │ │ ├── AliasedNodeInterface.php
│ │ │ │ │ └── FilterNodeInterface.php
│ │ │ │ ├── Metric.php
│ │ │ │ ├── QueryFilter.php
│ │ │ │ ├── Range.php
│ │ │ │ └── Term.php
│ │ │ ├── RequestParser.php
│ │ │ ├── Response.php
│ │ │ ├── SphinxQLRequest.php
│ │ │ └── TableFieldInfo.php
│ │ ├── LicenseHandler.php
│ │ ├── ManagerSettingsKibanaHandler.php
│ │ ├── MetricKibanaHandler.php
│ │ ├── MgetKibanaHandler.php
│ │ ├── NodesInfoKibanaHandler.php
│ │ ├── Payload.php
│ │ ├── QueryMap
│ │ │ ├── Cluster.php
│ │ │ ├── ManagerSettings.php
│ │ │ └── Settings.php
│ │ ├── QueryMapLoaderTrait.php
│ │ ├── SettingsKibanaHandler.php
│ │ ├── TableKibanaHandler.php
│ │ ├── TelemetryKibanaHandler.php
│ │ ├── UpdateEntityHandler.php
│ │ └── XpackInfoKibanaHandler.php
│ ├── Fuzzy
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Insert
│ │ ├── Error
│ │ │ ├── AutoSchemaDisabledError.php
│ │ │ └── ParserLoadError.php
│ │ ├── Handler.php
│ │ ├── Payload.php
│ │ └── QueryParser
│ │ │ ├── BaseParser.php
│ │ │ ├── CheckInsertDataTrait.php
│ │ │ ├── Datalim.php
│ │ │ ├── Datatype.php
│ │ │ ├── ElasticJSONInsertParser.php
│ │ │ ├── InsertQueryParserInterface.php
│ │ │ ├── JSONInsertParser.php
│ │ │ ├── JSONParser.php
│ │ │ ├── JSONParserInterface.php
│ │ │ ├── Loader.php
│ │ │ ├── QueryParserInterface.php
│ │ │ └── SQLInsertParser.php
│ ├── Knn
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Metrics
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── ModifyTable
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Plugin
│ │ ├── ActionType.php
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Queue
│ │ ├── Handlers
│ │ │ ├── BaseDropHandler.php
│ │ │ ├── BaseGetHandler.php
│ │ │ ├── BaseViewHandler.php
│ │ │ ├── Source
│ │ │ │ ├── BaseCreateSourceHandler.php
│ │ │ │ ├── CreateKafka.php
│ │ │ │ ├── DropSourceHandler.php
│ │ │ │ ├── GetSourceHandler.php
│ │ │ │ └── ViewSourceHandler.php
│ │ │ └── View
│ │ │ │ ├── AlterViewHandler.php
│ │ │ │ ├── CreateViewHandler.php
│ │ │ │ ├── DropViewHandler.php
│ │ │ │ ├── GetViewHandler.php
│ │ │ │ └── ViewViewsHandler.php
│ │ ├── Models
│ │ │ ├── Alter
│ │ │ │ └── AlterMaterializedViewModel.php
│ │ │ ├── Create
│ │ │ │ ├── CreateMaterializedViewModel.php
│ │ │ │ └── CreateSourceModel.php
│ │ │ ├── Drop
│ │ │ │ ├── DropMaterializedViewModel.php
│ │ │ │ └── DropSourceModel.php
│ │ │ ├── Factories
│ │ │ │ ├── AlterFactory.php
│ │ │ │ ├── CreateFactory.php
│ │ │ │ ├── DropFactory.php
│ │ │ │ └── ShowFactory.php
│ │ │ ├── Model.php
│ │ │ ├── Show
│ │ │ │ ├── ShowMaterializedViewModel.php
│ │ │ │ ├── ShowMaterializedViewsModel.php
│ │ │ │ ├── ShowSourceModel.php
│ │ │ │ └── ShowSourcesModel.php
│ │ │ └── SqlModelsHandler.php
│ │ ├── Payload.php
│ │ ├── QueueProcess.php
│ │ ├── StringFunctionsTrait.php
│ │ └── Workers
│ │ │ └── Kafka
│ │ │ ├── Batch.php
│ │ │ ├── KafkaWorker.php
│ │ │ └── View.php
│ ├── Replace
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Select
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Sharding
│ │ ├── Cluster.php
│ │ ├── CreateHandler.php
│ │ ├── DescHandler.php
│ │ ├── DropHandler.php
│ │ ├── Node.php
│ │ ├── Operator.php
│ │ ├── Payload.php
│ │ ├── Processor.php
│ │ ├── Queue.php
│ │ ├── State.php
│ │ ├── Table.php
│ │ ├── TableOperation.php
│ │ └── Util.php
│ ├── Show
│ │ ├── CreateTableHandler.php
│ │ ├── ExpandedTablesHandler.php
│ │ ├── FullColumnsHandler.php
│ │ ├── Payload.php
│ │ ├── QueriesHandler.php
│ │ ├── SchemasHandler.php
│ │ ├── UnsupportedStmtHandler.php
│ │ └── VersionHandler.php
│ ├── Test
│ │ ├── Handler.php
│ │ └── Payload.php
│ ├── Truncate
│ │ ├── Handler.php
│ │ └── Payload.php
│ └── Update
│ │ ├── Handler.php
│ │ └── Payload.php
├── func.php
├── init.php
└── main.php
└── test
├── Buddy
├── functional
│ ├── AutoSchemaSupportTest.php
│ ├── BackupTest.php
│ ├── BenchLoadTest.php
│ ├── BuildTest.php
│ ├── CliTableTest.php
│ ├── DebugModeTest.php
│ ├── DirectRequestTest.php
│ ├── HungRequestTest.php
│ ├── InsertQueryTest.php
│ ├── ListenArgTest.php
│ ├── MetricThreadTest.php
│ ├── MultipleQueriesTest.php
│ ├── OnStartOutputTest.php
│ ├── ProcessErrorTest.php
│ ├── ProcessKillTest.php
│ ├── ShowFullTablesTest.php
│ ├── ShowOpenTablesTest.php
│ ├── ShowQueriesTest.php
│ ├── ShowVariablesTest.php
│ └── config
│ │ ├── manticore-bench.conf
│ │ └── manticore.conf
└── src
│ ├── Lib
│ ├── CliArgsProcessorTest.php
│ └── QueryProcessorTest.php
│ ├── Sharding
│ └── UtilTest.php
│ └── Trait
│ └── CheckInsertDataTraitTest.php
├── Kafka
├── dump.json
└── import.sh
├── Plugin
├── Backup
│ └── BackupPayloadTest.php
├── CliTable
│ └── CliTableHandlerTest.php
├── EmulateElastic
│ ├── CreateTableHandlerTest.php
│ └── PayloadTest.php
├── Insert
│ ├── Exception
│ │ └── ParserLoadErrorTest.php
│ ├── InsertDataCheckTest.php
│ ├── InsertQuery
│ │ ├── InsertQueryHandlerTest.php
│ │ └── InsertQueryPayloadTest.php
│ └── QueryParser
│ │ ├── JSONInsertParserTest.php
│ │ ├── ParserLoaderTest.php
│ │ └── SQLInsertParserTest.php
├── Sharding
│ └── NodeTest.php
└── Show
│ ├── ShowFullOrOpenTables
│ └── ShowFullOrOpenTablesPayloadTest.php
│ └── ShowQueries
│ └── ShowQueriesHandlerTest.php
├── bootstrap.php
└── src
├── Lib
├── BuddyRequestError.php
├── MockManticoreServer.php
└── SocketError.php
└── Trait
├── TestFunctionalTrait.php
├── TestHTTPServerTrait.php
└── TestProtectedTrait.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [**.php]
13 | indent_style = tab
14 | indent_size = 2
15 |
16 | [**.yaml]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [**.yml]
21 | indent_style = space
22 | indent_size = 2
23 |
24 | [**.md]
25 | indent_style = space
26 | indent_size = 2
27 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | APP_VERSION export-subst
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: 🐞 Bug Report
2 | description: Submit a bug report for Manticore Buddy
3 | labels: bug
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thank you for submitting a bug report. We appreciate your effort to provide detailed information. Please answer the following questions to help us identify and fix the bug. Thank you!
9 | - type: textarea
10 | id: proposal
11 | attributes:
12 | label: "Bug Description:"
13 | description: >
14 | Describe the bug in detail. Include a [Minimal Reproducible Example](https://en.wikipedia.org/wiki/Minimal_reproducible_example) (MRE) if possible. Place any code blocks within triple backticks:
15 | value: |
16 | ```bash
17 | # Example code block; replace with your code if applicable
18 | ```
19 | validations:
20 | required: true
21 | - type: input
22 | id: version
23 | attributes:
24 | label: "Manticore Search Version:"
25 | description: >
26 | Provide the version of Manticore Search you are using. Execute `searchd -v` in the command line to find this information.
27 | validations:
28 | required: true
29 | - type: input
30 | id: os
31 | attributes:
32 | label: "Operating System Version:"
33 | description: >
34 | Specify the version of your operating system.
35 | validations:
36 | required: true
37 | - type: dropdown
38 | id: dev
39 | attributes:
40 | label: "Have you tried the latest development version?"
41 | multiple: false
42 | options:
43 | - "Yes"
44 | - "No"
45 | - type: markdown
46 | attributes:
47 | value: "## Thank you for completing the form! For an expedited solution, consider our [professional services](https://manticoresearch.com/services/)."
48 | - type: textarea
49 | id: checklist
50 | attributes:
51 | label: "Internal Checklist:"
52 | description: >
53 | **For Manticore Team Use Only** — Please do not edit this section. This checklist will be completed by the Manticore team as they manage the issue.
54 | value: |
55 | To be completed by the assignee. Check off tasks that have been completed or are not applicable.
56 |
57 |
58 | - [ ] Implementation completed
59 | - [ ] Tests developed
60 | - [ ] Documentation updated
61 | - [ ] Documentation reviewed
62 |
63 |
64 | validations:
65 | required: true
66 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | contact_links:
2 | - name: "Manticore Team's professional services"
3 | about: "Looking for a faster solution to your issues with Manticore? Manticore Team can help."
4 | url: "https://manticoresearch.com/services"
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: 🌟 Feature Request
2 | description: Submit a proposal for a new Manticore Buddy feature or enhancement
3 | body:
4 | - type: textarea
5 | id: proposal
6 | attributes:
7 | label: "Proposal:"
8 | description: >
9 | Please describe your proposal in detail.
10 | Include why you believe this feature should be added to Manticore Buddy and what use cases it supports.
11 | If applicable, add any examples or code snippets inside triple backticks to clarify your proposal.
12 | validations:
13 | required: true
14 | - type: markdown
15 | attributes:
16 | value: "## Thank you for completing the form! If you are interested in sponsoring the development of this feature, consider our [professional services](https://manticoresearch.com/services/)."
17 | - type: textarea
18 | id: checklist
19 | attributes:
20 | label: "Checklist:"
21 | description: >
22 | **For Manticore Team Use Only** — Please do not edit this section. This checklist will be completed by the Manticore team as they manage the issue.
23 | value: |
24 | To be completed by the assignee. Check off tasks that have been completed or are not applicable.
25 |
26 |
27 | - [ ] Implementation completed
28 | - [ ] Tests developed
29 | - [ ] Documentation updated
30 | - [ ] Documentation reviewed
31 | - [x] OpenAPI YAML updated and issue created to rebuild clients
32 |
33 |
34 | validations:
35 | required: true
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/support_request.yml:
--------------------------------------------------------------------------------
1 | name: ❓ Support Request
2 | description: Need help with Manticore? Submit your questions here!
3 | body:
4 | - type: checkboxes
5 | id: dev
6 | attributes:
7 | label: "Confirmation Checklist:"
8 | description: >
9 | Before submitting your request, we ask that you confirm the following items to ensure that you receive the most effective support:
10 | options:
11 | - label: "You have searched for an answer in [the manual](https://manual.manticoresearch.com/)."
12 | - label: "You have considered using [the forum](https://forum.manticoresearch.com/) for general discussions, which can be more suitable for non-urgent or broad queries."
13 | - label: "You are aware of our community support channels on [Slack](https://slack.manticoresearch.com/), [Telegram EN](https://t.me/manticoresearch_en), and [Telegram RU](https://t.me/manticore_chat), where you can interact with other users and our developers."
14 | - label: "You know about Manticore Team's [professional services](https://manticoresearch.com/services). Engaging with our experts through a support subscription can significantly accelerate resolution times and provide tailored solutions to your specific needs."
15 | validations:
16 | required: true
17 | - type: textarea
18 | id: question
19 | attributes:
20 | label: "Your question:"
21 | description: >
22 | Please provide detailed information about your question to ensure prompt and accurate help!
23 | validations:
24 | required: true
25 | - type: markdown
26 | attributes:
27 | value: "## Thank you for completing the form! If you need immediate, dedicated support or a long-term support subscription, consider using our [professional services](https://manticoresearch.com/services)."
28 |
--------------------------------------------------------------------------------
/.github/workflows/checklist_validator.yml:
--------------------------------------------------------------------------------
1 | name: 📝 Checklist Validator
2 | run-name: 📝 Checklist Validator for issue ${{ github.event.issue.number }}
3 |
4 | on:
5 | issues:
6 | types:
7 | - closed
8 |
9 | jobs:
10 | checklist-validation:
11 | name: ✅ Checklist Completion Check
12 | runs-on: ubuntu-22.04
13 | steps:
14 | - uses: manticoresoftware/manticoresearch/actions/checklist-validator@master
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/**
2 | tmp/**
3 | build/**
4 | .phpunit.result.cache
5 | /.ptp-sync/
6 | .ptp-sync-folder
7 | phar_builder
8 | plugins/*
9 | !plugins/.gitkeep
10 | bin/manticore-*.conf
11 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | # See https://pre-commit.com for more information
2 | # See https://pre-commit.com/hooks.html for more hooks
3 | repos:
4 | - repo: https://github.com/pre-commit/pre-commit-hooks
5 | rev: v2.3.0
6 | hooks:
7 | - id: check-yaml
8 | - id: end-of-file-fixer
9 | - id: trailing-whitespace
10 | - id: check-executables-have-shebangs
11 | - id: check-added-large-files
12 | - repo: local
13 | hooks:
14 | - id: codestyle-fix
15 | name: codestyle-fix
16 | entry: bin/codestyle-fix
17 | language: system
18 | types: [php]
19 | pass_filenames: false
20 | - id: codestyle
21 | name: codestyle
22 | entry: bin/codestyle
23 | language: system
24 | types: [php]
25 | pass_filenames: false
26 | - id: codeanalyze
27 | name: codeanalyze
28 | entry: bin/codeanalyze
29 | language: system
30 | types: [php]
31 | pass_filenames: false
32 |
--------------------------------------------------------------------------------
/.sync:
--------------------------------------------------------------------------------
1 | vendor/*
2 | plugins/*
3 |
--------------------------------------------------------------------------------
/APP_VERSION:
--------------------------------------------------------------------------------
1 | 3.30.2
2 |
--------------------------------------------------------------------------------
/Hello:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manticoresoftware/manticoresearch-buddy/c16d9c261ef4b3b656fe1c2d4e8f846e8f42f3c4/Hello
--------------------------------------------------------------------------------
/PHP_VERSION:
--------------------------------------------------------------------------------
1 | 8.1
2 |
--------------------------------------------------------------------------------
/bin/cluster:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4 | if [ -z "$1" ]; then
5 | echo "Usage: $0 "
6 | exit 1
7 | fi
8 | nodes=$1
9 | if ((nodes < 2)); then
10 | echo "Number of nodes must be greater than 1"
11 | exit 1
12 | fi
13 | if ((nodes > 5)); then
14 | echo "Number of nodes must be less than or equal to 5"
15 | exit 1
16 | fi
17 |
18 | for n in $(seq 1 $nodes); do
19 | cat << EOF > $DIR/manticore-${n}.conf
20 | searchd {
21 | buddy_path = manticore-executor /workdir/src/main.php --log-level=debugvv
22 | listen = 127.0.0.1:${n}9312
23 | listen = 127.0.0.1:${n}9306:mysql
24 | listen = 127.0.0.1:${n}9308:http
25 | log = /var/log/manticore/searchd-$n.log
26 | query_log = /var/log/manticore/query-$n.log
27 | pid_file = /var/run/manticore/searchd-$n.pid
28 | data_dir = /var/lib/manticore/$n
29 | }
30 | EOF
31 | done
32 |
33 | # Function to stop processes
34 | stop_processes() {
35 | echo "Stopping searchd processes..."
36 |
37 | for n in $(seq 1 $nodes); do
38 | searchd --config "$DIR/manticore-${n}.conf" --stop
39 | done
40 | exit 0
41 | }
42 |
43 | # Set up trap to catch Cmd+C (SIGINT)
44 | trap stop_processes SIGINT
45 |
46 | # Start both searchd processes in the background and redirect output to console
47 | for n in $(seq 1 $nodes); do
48 | test -d /var/lib/manticore/$n && rm -rf $_
49 | mkdir -p /var/lib/manticore/$n
50 | searchd --config "$DIR/manticore-${n}.conf" --nodetach > >(sed 's/^/['$n'] /') 2>&1 &
51 | done
52 |
53 | # Wait for all searchd processes to start
54 | sleep 2
55 |
56 | # Creating cluster
57 | mysql -h0 -P19306 -e 'CREATE CLUSTER c'
58 | for n in $(seq 2 $nodes); do
59 | mysql -h0 -P"${n}9306" -e "JOIN CLUSTER c at '127.0.0.1:19312'"
60 | done
61 |
62 | echo "All searchd processes started. Press Cmd+C to stop."
63 |
64 | # Wait indefinitely
65 | while true; do
66 | sleep 1
67 | done
68 |
--------------------------------------------------------------------------------
/bin/codeanalyze:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | ./vendor/bin/phpstan --memory-limit=-1 analyse -c "$(pwd)/phpstan.neon"
3 |
--------------------------------------------------------------------------------
/bin/codestyle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | ./vendor/bin/phpcs -v --no-cache -s --standard="$(pwd)/ruleset.xml" src/ test/
3 |
--------------------------------------------------------------------------------
/bin/codestyle-fix:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | ./vendor/bin/phpcbf -v --no-cache -s --standard="$(pwd)/ruleset.xml" src/ test/
3 |
--------------------------------------------------------------------------------
/bin/git-changelog:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo '## Changes'
4 | echo
5 |
6 | readarray -t pair < <(git tag --sort=-version:refname | head -n 2)
7 | readarray -t lines < <(git --no-pager log --format="%cI %H [%cN] %s" "${pair[1]}..${pair[0]}")
8 |
9 | for line in "${lines[@]}"; do
10 | date=$(echo "$line" | cut -d' ' -f1)
11 | commit=$(echo "$line" | cut -d' ' -f2)
12 | # author=$(echo "$line" | cut -d' ' -f3)
13 | message=$(echo "$line" | cut -d' ' -f4)
14 |
15 | repl="**${date}**"
16 | line=${line//$date/$repl}
17 |
18 | repl="[${commit:0:7}](https://github.com/manticoresoftware/manticoresearch-backup/commit/${commit})"
19 | line=${line//$commit/$repl}
20 |
21 | repl="*${message}*"
22 | line=${line//$message/$repl}
23 |
24 | echo "$line"
25 | done
26 |
--------------------------------------------------------------------------------
/bin/query:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | query="$1"
3 | if [[ -z "$query" ]]; then
4 | echo >&2 "Usage: $0 [query]"
5 | exit 1
6 | fi
7 |
8 | executor=$(which manticore-executor 2> /dev/null || which php 2> /dev/null)
9 | if [[ -z "$executor" ]]; then
10 | echo >&2 'You should install manticore-executor or PHP'
11 | exit 1
12 | fi
13 |
14 | query=${query//\'/\\\'}
15 | $executor -n < Buddy::PROTOCOL_VERSION,
29 | 'type' => 'unknown json request',
30 | 'error' => '',
31 | 'message' => [
32 | 'path_query' => '/cli',
33 | 'body' => '$query',
34 | ]
35 | ]);
36 | \$task = QueryProcessor::process(\$request)->run();
37 | \$status = \$task->wait(true);
38 | printf('Status code: %s' . PHP_EOL, \$status->name);
39 | printf('Result: ' . PHP_EOL . '%s', \$task->getResult()->getStruct());
40 | } catch (Throwable \$e) {
41 | echo 'Error:' . PHP_EOL;
42 | echo ' ' . \$e::class . ': ' . \$e->getMessage() . PHP_EOL;
43 | exit(1);
44 | }
45 | echo 'done' . PHP_EOL;
46 |
47 | CODE
48 |
--------------------------------------------------------------------------------
/bin/test:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | ./vendor/bin/phpunit -d memory_limit=4G --stop-on-failure "$@" test/
3 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "manticoresoftware/manticoresearch-buddy",
3 | "description": "Buddy assistant for the Manticore Search",
4 | "keywords": [
5 | "search",
6 | "backup",
7 | "manticoresearch"
8 | ],
9 | "license": "GPL-2.0-or-later",
10 | "type": "project",
11 | "config": {
12 | "platform": {
13 | "php": "8.1.0"
14 | },
15 | "allow-plugins": {
16 | "dealerdirect/phpcodesniffer-composer-installer": true
17 | }
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "Manticoresearch\\Buddy\\Base\\": "src/"
22 | },
23 | "files": ["src/func.php"]
24 | },
25 | "autoload-dev": {
26 | "psr-4": {
27 | "Manticoresearch\\BuddyTest\\": [
28 | "test/src"
29 | ]
30 | }
31 | },
32 | "require": {
33 | "manticoresoftware/telemetry": "^0.1.19",
34 | "symfony/dependency-injection": "^6.1",
35 | "manticoresoftware/buddy-core": "dev-main",
36 | "php-ds/php-ds": "^1.4",
37 | "manticoresoftware/manticoresearch-backup": "^1.3",
38 | "symfony/expression-language": "^6.4"
39 | },
40 | "require-dev": {
41 | "phpstan/phpstan": "^1.8",
42 | "slevomat/coding-standard": "^8.5",
43 | "squizlabs/php_codesniffer": "^3.7",
44 | "phpunit/phpunit": "^9.5",
45 | "kwn/php-rdkafka-stubs": "^2.2",
46 | "swoole/ide-helper": "~5.0.0"
47 | },
48 | "repositories": [
49 | {
50 | "type": "path",
51 | "url": "./plugins/*"
52 | }
53 | ],
54 | "bin": ["manticore-buddy"]
55 | }
56 |
--------------------------------------------------------------------------------
/docker-compose-dev.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | kafka:
5 | image: docker.io/bitnami/kafka:3.7
6 | profiles: [queues]
7 | container_name: kafka
8 | networks:
9 | - app-network
10 | volumes:
11 | - ./test/Kafka/import.sh:/import.sh
12 | - ./test/Kafka/dump.json:/tmp/dump.json
13 | environment:
14 | # KRaft settings
15 | - KAFKA_CFG_NODE_ID=0
16 | - KAFKA_CFG_PROCESS_ROLES=controller,broker
17 | - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093
18 | # Listeners
19 | - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
20 | - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://:9092
21 | - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
22 | - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
23 | - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
24 | buddy:
25 | image: ghcr.io/manticoresoftware/manticoresearch:test-kit-latest
26 | container_name: manticore-buddy
27 | privileged: true
28 | tty: true
29 | entrypoint:
30 | - "/bin/sh"
31 | - "-c"
32 | - "grep buddy_path /etc/manticoresearch/manticore.conf > /dev/null 2>&1 || sed -i '/searchd {/a \\ buddy_path = manticore-executor /workdir/src/main.php --debugvv' /etc/manticoresearch/manticore.conf && sed -i '/^searchd {/a \\ listen = /var/run/mysqld/mysqld.sock:mysql41' /etc/manticoresearch/manticore.conf; exec /bin/bash"
33 | working_dir: "/workdir"
34 | networks:
35 | - app-network
36 | volumes:
37 | - ./:/workdir/
38 | networks :
39 | app-network :
40 | driver : bridge
41 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | manticore-executor:
4 | cap_add:
5 | - SYS_ADMIN
6 | build:
7 | context: .
8 | args:
9 | TARGET_ARCH: amd64
10 | working_dir: /var/www
11 | volumes:
12 | - .:/var/www
13 |
--------------------------------------------------------------------------------
/packages/rpm.spec:
--------------------------------------------------------------------------------
1 | Summary: {{ DESC }}
2 | Name: {{ NAME }}
3 | Version: {{ VERSION }}
4 | Release: 1%{?dist}
5 | Group: Applications
6 | License: GPLv2
7 | Packager: {{ MAINTAINER }}
8 | Vendor: {{ MAINTAINER }}
9 | Requires: {{ LIBCURL_NAME }} >= {{ LIBCURL_VERSION }}
10 |
11 | Source: tmp.tar.gz
12 | BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot
13 | BuildArch: noarch
14 |
15 | %description
16 | {{ DESC }}
17 |
18 | %prep
19 | rm -rf %{buildroot}
20 |
21 | %setup -n %{name}
22 |
23 | %build
24 |
25 | %install
26 | mkdir -p %{buildroot}/usr/share/manticore/modules
27 | cp -rp usr/share/manticore/modules/{{ NAME }} %{buildroot}/usr/share/manticore/modules/{{ NAME }}
28 |
29 | %clean
30 | rm -rf %{buildroot}
31 |
32 | %post
33 |
34 | %postun
35 |
36 | %files
37 | %defattr(-, root, root)
38 | %dir /usr/share/manticore/modules/{{ NAME }}
39 | %dir /usr/share/manticore/modules/{{ NAME }}/bin
40 | /usr/share/manticore/modules/{{ NAME }}/src/*
41 | /usr/share/manticore/modules/{{ NAME }}/vendor/*
42 | /usr/share/manticore/modules/{{ NAME }}/APP_VERSION
43 | /usr/share/manticore/modules/{{ NAME }}/composer.json
44 | /usr/share/manticore/modules/{{ NAME }}/composer.lock
45 | %attr(1755, root, root) /usr/share/manticore/modules/{{ NAME }}/bin/{{ NAME }}
46 |
47 | %changelog
48 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | paths:
3 | - src
4 | - test
5 | level: 9
6 | inferPrivatePropertyTypeFromConstructor: true
7 | checkGenericClassInNonGenericObjectType: true
8 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | src/
6 |
7 |
8 | test/
9 |
10 |
11 |
12 | test/
13 |
14 |
15 |
--------------------------------------------------------------------------------
/plugins/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manticoresoftware/manticoresearch-buddy/c16d9c261ef4b3b656fe1c2d4e8f846e8f42f3c4/plugins/.gitkeep
--------------------------------------------------------------------------------
/src/Config/LogLevel.php:
--------------------------------------------------------------------------------
1 | 'info',
21 | self::Debug => 'debug',
22 | self::Debugv => 'debugv',
23 | self::Debugvv => 'debugvv',
24 | };
25 | }
26 |
27 | /**
28 | * Create an enum instance from a string name
29 | * @param string $name
30 | * @return static
31 | */
32 | public static function fromString(string $name): static {
33 | return match (strtolower($name)) {
34 | 'info' => self::Info,
35 | 'debug' => self::Debug,
36 | 'debugv' => self::Debugv,
37 | 'debugvv' => self::Debugvv,
38 | default => throw new \InvalidArgumentException("Invalid log level $name"),
39 | };
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Exception/SQLQueryCommandMissing.php:
--------------------------------------------------------------------------------
1 | query);
40 | if (!$query) {
41 | throw new RuntimeException('Failed to prepare query');
42 | }
43 |
44 | $resp = $manticoreClient
45 | ->sendRequest($query, $payload->path);
46 | return TaskResult::fromResponse($resp);
47 | };
48 |
49 | return Task::create(
50 | $taskFn,
51 | [$this->payload, $this->manticoreClient]
52 | )->run();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Plugin/Alias/Handler.php:
--------------------------------------------------------------------------------
1 | query);
40 | if (!$query) {
41 | throw new RuntimeException('Failed to prepare query');
42 | }
43 |
44 | $queryResponse = $manticoreClient
45 | ->sendRequest($query, $payload->path);
46 | return TaskResult::fromResponse($queryResponse);
47 | };
48 |
49 | return Task::create(
50 | $taskFn,
51 | [$this->payload, $this->manticoreClient]
52 | )->run();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Plugin/Alias/Payload.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | final class Payload extends BasePayload {
21 | /** @var string */
22 | public string $path;
23 |
24 | /** @var string */
25 | public string $query;
26 |
27 | public function __construct() {
28 | }
29 |
30 | /**
31 | * @param Request $request
32 | * @return static
33 | */
34 | public static function fromRequest(Request $request): static {
35 | $self = new static();
36 | $self->path = $request->path;
37 | $self->query = $request->payload;
38 | return $self;
39 | }
40 |
41 | /**
42 | * @param Request $request
43 | * @return bool
44 | */
45 | public static function hasMatch(Request $request): bool {
46 | $hasError = stripos($request->error, 'near') !== false && (
47 | str_contains($request->error, "unexpected \$undefined near '.*")
48 | || str_contains($request->error, "unexpected identifier, expecting SET near 't ")
49 | || (
50 | str_contains($request->error, "expecting \$end near '")
51 | && str_contains($request->error, " t'")
52 | )
53 | );
54 | if (!$hasError) {
55 | return false;
56 | }
57 |
58 | return stripos($request->payload, ' t.') !== false
59 | || str_ends_with($request->payload, ' t');
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Plugin/Backup/Handler.php:
--------------------------------------------------------------------------------
1 | payload->options['async'] ?? false;
45 | $task = Task::create(
46 | static function (string $args): TaskResult {
47 | /** @var Payload $payload */
48 | /** @phpstan-ignore-next-line */
49 | [$payload] = unserialize($args);
50 | $config = new ManticoreConfig($payload->configPath);
51 | $client = new ManticoreClient([$config]);
52 | $storage = new FileStorage(
53 | $payload->path,
54 | $payload->options['compress'] ?? false
55 | );
56 | ManticoreBackup::run('store', [$client, $storage, $payload->tables]);
57 | ;
58 | return TaskResult::withRow(
59 | [
60 | 'Path' => $storage->getBackupPaths()['root'],
61 | ]
62 | )->column('Path', Column::String);
63 | },
64 | [serialize([$this->payload])]
65 | );
66 | if ($isAsync) {
67 | $task->defer();
68 | }
69 | return $task->run();
70 | }
71 |
72 | /**
73 | * @return array
74 | */
75 | public function getProps(): array {
76 | return [];
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Plugin/CliTable/Handler.php:
--------------------------------------------------------------------------------
1 | sendRequest(
48 | $payload->query,
49 | $payload->path,
50 | disableAgentHeader: true
51 | );
52 | return TaskResult::fromResponse($resp);
53 | };
54 |
55 | return Task::create(
56 | $taskFn,
57 | [$this->payload, $this->manticoreClient]
58 | )->run();
59 | }
60 |
61 | /**
62 | * @param array $resultInfo
63 | * @param array> $data
64 | * @param int $total
65 | * @return void
66 | */
67 | protected static function processResultInfo(array $resultInfo, ?array &$data = [], int &$total = -1): void {
68 | if (isset($resultInfo['data']) && is_array($resultInfo['data'])) {
69 | $data = $resultInfo['data'];
70 | }
71 | if (!isset($resultInfo['total'])) {
72 | return;
73 | }
74 | $total = $resultInfo['total'];
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/Plugin/CliTable/Payload.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | final class Payload extends BasePayload {
23 | public string $query;
24 | public string $path;
25 |
26 | /**
27 | * Get description for this plugin
28 | * @return string
29 | */
30 | public static function getInfo(): string {
31 | return '/cli endpoint based on /cli_json - outputs query result as a table';
32 | }
33 |
34 | /**
35 | * @param Request $request
36 | * @return static
37 | */
38 | public static function fromRequest(Request $request): static {
39 | $self = new static();
40 | $self->query = $request->payload;
41 | $self->path = ManticoreEndpoint::Sql->value;
42 | return $self;
43 | }
44 |
45 | /**
46 | * @param Request $request
47 | * @return bool
48 | */
49 | public static function hasMatch(Request $request): bool {
50 | return $request->endpointBundle === ManticoreEndpoint::Cli;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Plugin/CreateCluster/Handler.php:
--------------------------------------------------------------------------------
1 | manticoreClient->sendRequest($this->payload->query);
39 | $error = $resp->getError();
40 | // In case quiet mode we have IF EXISTS so do nothing
41 | if ($error && $this->payload->quiet) {
42 | return TaskResult::none();
43 | }
44 | return TaskResult::fromResponse($resp);
45 | };
46 |
47 | return Task::create(
48 | $taskFn, [$this->payload, $this->manticoreClient]
49 | )->run();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Plugin/CreateCluster/Payload.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | final class Payload extends BasePayload {
22 | public string $query;
23 | public bool $quiet = false;
24 |
25 | /**
26 | * Get description for this plugin
27 | * @return string
28 | */
29 | public static function getInfo(): string {
30 | return 'Enable CREATE CLUSTER IF NOT EXISTS statements';
31 | }
32 |
33 | /**
34 | * @param Request $request
35 | * @return static
36 | */
37 | public static function fromRequest(Request $request): static {
38 | $self = new static();
39 |
40 | $payload = $request->payload;
41 | $self->query = $payload;
42 | if (preg_match('/IF\s+NOT\s+EXISTS/ius', $payload)) {
43 | $self->query = preg_replace('/\s+IF\s+NOT\s+EXISTS/ius', '', $payload) ?: $payload;
44 | $self->quiet = true;
45 | }
46 |
47 | return $self;
48 | }
49 |
50 | /**
51 | * @param Request $request
52 | * @return bool
53 | * @throws GenericError
54 | */
55 | public static function hasMatch(Request $request): bool {
56 | return $request->command === 'create'
57 | && stripos($request->payload, 'create cluster') === 0
58 | && stripos($request->error, 'P03') !== false;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Plugin/CreateTable/WithEngineHandler.php:
--------------------------------------------------------------------------------
1 | destinationTableName}";
43 | $result = $client->sendRequest($sql);
44 | if ($result->hasError()) {
45 | throw GenericError::create(
46 | "Can't create table {$payload->destinationTableName}. Reason: " . $result->getError()
47 | );
48 | }
49 | return TaskResult::none();
50 | };
51 |
52 | return Task::create(
53 | $taskFn, [$this->payload, $this->manticoreClient]
54 | )->run();
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/Plugin/Drop/Handler.php:
--------------------------------------------------------------------------------
1 | table}";
41 | $result = $client->sendRequest($stmt);
42 | if ($result->hasError()) {
43 | throw GenericError::create(
44 | "Can't drop table {$payload->table}" .
45 | "Reason: {$result->getError()}"
46 | );
47 | }
48 |
49 | return TaskResult::none();
50 | };
51 |
52 | return Task::create(
53 | $taskFn, [$this->payload, $this->manticoreClient]
54 | )->run();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Plugin/Drop/Payload.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | final class Payload extends BasePayload
24 | {
25 | public string $table;
26 |
27 | /**
28 | * Get description for this plugin
29 | * @return string
30 | */
31 | public static function getInfo(): string {
32 | return 'Handles DROP statements with MySQL options not supported by Manticore';
33 | }
34 |
35 | /**
36 | * @param Request $request
37 | * @return static
38 | */
39 | public static function fromRequest(Request $request): static {
40 | $self = new static();
41 |
42 | $matches = [];
43 | if (preg_match('/(Manticore|`Manticore`)\.(\S+)\s*$/i', $request->payload, $matches)) {
44 | $self->table = $matches[2];
45 | return $self;
46 | }
47 |
48 | throw QueryParseError::create('Failed to handle your DROP query', true);
49 | }
50 |
51 | /**
52 | * @param Request $request
53 | * @return bool
54 | */
55 | public static function hasMatch(Request $request): bool {
56 | return (stripos($request->error, "P01: syntax error, unexpected identifier near 'DROP TABLE") === 0);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Plugin/EmptyString/Handler.php:
--------------------------------------------------------------------------------
1 | run();
40 | }
41 |
42 | /**
43 | * @return array
44 | */
45 | public function getProps(): array {
46 | return [];
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Plugin/EmptyString/Payload.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | final class Payload extends BasePayload {
23 | public string $path;
24 |
25 | /**
26 | * Get description for this plugin
27 | * @return string
28 | */
29 | public static function getInfo(): string {
30 | return 'Handles empty queries,'
31 | . ' which can occur when trimming comments or dealing with specific SQL'
32 | . ' protocol instructions in comments that are not supported';
33 | }
34 |
35 | /**
36 | * @param Request $request
37 | * @return static
38 | */
39 | public static function fromRequest(Request $request): static {
40 | $self = new static();
41 | // We just need to do something, but actually its' just for PHPstan
42 | $self->path = $request->path;
43 | return $self;
44 | }
45 |
46 | /**
47 | * @param Request $request
48 | * @return bool
49 | */
50 | public static function hasMatch(Request $request): bool {
51 | $payload = strtolower($request->payload);
52 | if ($request->payload === ''
53 | && $request->endpointBundle !== Endpoint::Metrics
54 | && $request->endpointBundle !== Endpoint::Bulk
55 | && $request->endpointBundle !== Endpoint::Elastic) {
56 | return true;
57 | }
58 | if ($request->command === 'set') {
59 | $setPatterns = [
60 | 'sql_quote_show_create',
61 | '@saved_cs_client',
62 | '@@session',
63 | 'character_set_client',
64 | 'session character_set_results',
65 | 'session transaction',
66 | 'sql_select_limit',
67 | ];
68 | foreach ($setPatterns as $pattern) {
69 | if (stripos($payload, $pattern) === 4) {
70 | return true;
71 | }
72 | }
73 | }
74 |
75 | return match ($request->command) {
76 | 'create' => stripos($payload, 'create database') === 0,
77 | 'lock' => stripos($payload, 'lock tables') === 0,
78 | 'unlock' => stripos($payload, 'unlock tables') === 0,
79 | default => false,
80 | };
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/CatHandler.php:
--------------------------------------------------------------------------------
1 | path);
45 | if (!isset($pathParts[1], $pathParts[2])
46 | && !in_array($pathParts[1], self::CAT_ENTITIES) && !str_ends_with($pathParts[1], '*')) {
47 | throw new \Exception('Cannot parse request');
48 | }
49 | $entityTable = "_{$pathParts[1]}";
50 | $entityNamePattern = $pathParts[2];
51 |
52 | $query = "SELECT * FROM {$entityTable} WHERE MATCH('{$entityNamePattern}')";
53 | /** @var array{0:array{data?:array}} $queryResult */
54 | $queryResult = $manticoreClient->sendRequest($query)->getResult();
55 | if (!isset($queryResult[0]['data']) || !$queryResult[0]['data']) {
56 | return TaskResult::raw([]);
57 | }
58 |
59 | $catInfo = [];
60 | foreach ($queryResult[0]['data'] as $entityInfo) {
61 | $catInfo[] = [
62 | 'name' => $entityInfo['name'],
63 | 'order' => 0,
64 | 'index_patterns' => simdjson_decode($entityInfo['patterns'], true),
65 | ] + simdjson_decode($entityInfo['content'], true);
66 | }
67 |
68 | return TaskResult::raw($catInfo);
69 | };
70 |
71 | return Task::create(
72 | $taskFn, [$this->payload, $this->manticoreClient]
73 | )->run();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/ClusterKibanaHandler.php:
--------------------------------------------------------------------------------
1 | payload->path);
42 | }
43 |
44 | /**
45 | * @return array
46 | */
47 | public function getProps(): array {
48 | return [];
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/CountInfoKibanaHandler.php:
--------------------------------------------------------------------------------
1 | [
45 | 'failed' => 0,
46 | 'skipped' => 0,
47 | 'successful' => 1,
48 | 'total' => 1,
49 | ],
50 | 'count' => 0,
51 | ]
52 | );
53 | };
54 |
55 | return Task::create($taskFn)->run();
56 | }
57 |
58 | /**
59 | * @return array
60 | */
61 | public function getProps(): array {
62 | return [];
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/FindEntityHandler.php:
--------------------------------------------------------------------------------
1 | path, $manticoreClient);
42 |
43 | $query = 'SELECT _source FROM `' . self::ENTITY_TABLE
44 | . "` WHERE _id='{$entityId}' AND _index='{$entityIndex}'";
45 | /** @var array{error?:string,0:array{data?:array}} $queryResult */
46 | $queryResult = $manticoreClient->sendRequest($query)->getResult();
47 | if (isset($queryResult['error']) || !isset($queryResult[0]['data']) || !$queryResult[0]['data']) {
48 | $resp = [
49 | '_id' => $entityId,
50 | '_index' => $entityIndex,
51 | '_type' => '_doc',
52 | 'found' => false,
53 | ];
54 | } else {
55 | $resp = [
56 | '_id' => $entityId,
57 | '_index' => $entityIndex,
58 | '_primary_term' => 1,
59 | '_seq_no' => 0,
60 | '_source' => simdjson_decode($queryResult[0]['data'][0]['_source'], true),
61 | '_type' => '_doc',
62 | '_version' => 1,
63 | 'found' => true,
64 | ];
65 | }
66 |
67 | return TaskResult::raw($resp);
68 | };
69 |
70 | return Task::create(
71 | $taskFn, [$this->payload, $this->manticoreClient]
72 | )->run();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/GetAliasesHandler.php:
--------------------------------------------------------------------------------
1 | }} $queryResult */
43 | $queryResult = $manticoreClient->sendRequest($query)->getResult();
44 | if (!isset($queryResult[0])) {
45 | return TaskResult::raw([]);
46 | }
47 |
48 | $aliasInfo = self::get($payload->table, $manticoreClient);
49 | return TaskResult::raw($aliasInfo);
50 | };
51 |
52 | return Task::create(
53 | $taskFn, [$this->payload, $this->manticoreClient]
54 | )->run();
55 | }
56 |
57 | /**
58 | *
59 | * @param string $indexAlias
60 | * @param HttpClient $manticoreClient
61 | * @return array
62 | */
63 | public static function get(string $indexAlias, HTTPClient $manticoreClient): array {
64 | $query = 'SELECT index FROM ' . parent::ALIAS_TABLE . " WHERE alias='{$indexAlias}'";
65 | /** @var array{0:array{data?:array}} $queryResult */
66 | $queryResult = $manticoreClient->sendRequest($query)->getResult();
67 | if (!isset($queryResult[0]['data']) || !$queryResult[0]['data']) {
68 | return [];
69 | }
70 | $aliasInfo = [];
71 | foreach ($queryResult[0]['data'] as $dataRow) {
72 | $aliasInfo[$dataRow['index']] = [
73 | 'aliases' => [
74 | $indexAlias => [],
75 | ],
76 | ];
77 | }
78 | return $aliasInfo;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/InitKibanaHandler.php:
--------------------------------------------------------------------------------
1 | path;
42 | $query = 'SELECT _id, _index, _source FROM `'
43 | . self::ENTITY_TABLE . "` WHERE _index_alias='{$alias}' AND _type='settings'";
44 | /** @var array{error?:string,0:array{data?:array}} $queryResult */
45 | $queryResult = $manticoreClient->sendRequest($query)->getResult();
46 | if (isset($queryResult['error']) || !isset($queryResult[0]['data']) || !$queryResult[0]['data']) {
47 | $resp = [
48 | 'error' => [
49 | 'index' => $alias,
50 | 'index_uuid' => '_na_',
51 | 'reason' => "no such index [{$alias}]",
52 | 'resource.id' => $alias,
53 | 'resource.type' => 'index_or_alias',
54 | 'root_cause' => [
55 | [
56 | 'index' => $alias,
57 | 'index_uuid' => '_na_',
58 | 'reason' => "no such index [{$alias}]",
59 | 'resource.id' => $alias,
60 | 'resource.type' => 'index_or_alias',
61 | 'type' => 'index_not_found_exception',
62 | ],
63 | ],
64 | 'type' => 'index_not_found_exception',
65 | ],
66 | 'status' => 404,
67 | ];
68 | } else {
69 | $resp = [];
70 | foreach ($queryResult[0]['data'] as $entity) {
71 | $resp[$entity['_index']] = [
72 | 'aliases' => [
73 | $alias => [],
74 | ],
75 | ] + simdjson_decode($entity['_source'], true);
76 | }
77 | }
78 |
79 | return TaskResult::raw($resp);
80 | };
81 |
82 | return Task::create(
83 | $taskFn, [$this->payload, $this->manticoreClient]
84 | )->run();
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/InvalidSourceKibanaHandler.php:
--------------------------------------------------------------------------------
1 | body, true);
44 | if (!is_array($request)) {
45 | throw new Exception("Invalid request passed: {$payload->body}");
46 | }
47 | $request['_source'] = [];
48 | $request['table'] = $payload->table;
49 | if (isset($request['script_fields'])) {
50 | unset($request['script_fields']);
51 | }
52 | $isGetSingleDocQuery = isset($request['query']) && is_array($request['query'])
53 | && isset($request['query']['ids']) && is_array($request['query']['ids'])
54 | && isset($request['query']['ids']['values']) && is_array($request['query']['ids']['values']);
55 | if ($isGetSingleDocQuery) {
56 | $ids = array_map(fn($id) => (int)$id, $request['query']['ids']['values']);
57 | $request['query']['in'] = [
58 | 'id' => $ids,
59 | ];
60 | }
61 | $query = json_encode($request);
62 | /** @var array{error?:string} $queryResult */
63 | $queryResult = $manticoreClient->sendRequest((string)$query, 'search')->getResult();
64 |
65 | return TaskResult::raw($queryResult);
66 | };
67 |
68 | return Task::create(
69 | $taskFn, [$this->payload, $this->manticoreClient]
70 | )->run();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Request/Aliasing.php:
--------------------------------------------------------------------------------
1 | $aliasedNodes
29 | * @param array $fieldNames
30 | */
31 | public function __construct(private array $aliasedNodes, private array $fieldNames) {
32 | }
33 |
34 | /**
35 | * @return static
36 | */
37 | public function apply(): static {
38 | foreach ($this->aliasedNodes as $node) {
39 | if ($node->getFieldAlias()) {
40 | continue;
41 | }
42 | $alias = $this->generateAlias();
43 | $node->setFieldAlias($alias);
44 | }
45 |
46 | return $this;
47 | }
48 |
49 | /**
50 | * @return string
51 | */
52 | public function generateAlias(): string {
53 | $alias = static::ALIAS_PREFIX . ++$this->aliasCount;
54 | while (in_array($alias, $this->fieldNames)) {
55 | $alias .= '_';
56 | }
57 | return $alias;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Request/Executor.php:
--------------------------------------------------------------------------------
1 | $logics */
25 | protected array $logics = [];
26 |
27 | /**
28 | * @param Factory $logicFactory
29 | */
30 | public function __construct(protected Factory $logicFactory) {
31 | }
32 |
33 | /**
34 | * @return static
35 | */
36 | public function init(): static {
37 | $this->logics = array_map(
38 | fn ($logicName) => $this->logicFactory->create($logicName),
39 | static::LOGIC_NAMES
40 | );
41 |
42 | return $this;
43 | }
44 |
45 | /**
46 | * @return bool
47 | */
48 | public function execute(): bool {
49 | foreach ($this->logics as $logic) {
50 | $logic->apply();
51 | if (!($logic instanceof FailableLogicInterface)) {
52 | continue;
53 | }
54 | if ($logic->isFailed()) {
55 | return false;
56 | }
57 | }
58 | return true;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Request/Interfaces/FailableLogicInterface.php:
--------------------------------------------------------------------------------
1 | > $responseRows */
23 | protected array $responseRows = [];
24 |
25 | /**
26 | * @param array> $responseRows
27 | * @return static
28 | */
29 | public function setResponseRows(array $responseRows): static {
30 | $this->responseRows = $responseRows;
31 | return $this;
32 | }
33 |
34 | /**
35 | * @return array>
36 | */
37 | public function getResponseRows(): array {
38 | return $this->responseRows;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Response/ConcurrentFilterProcessing/FilterSet.php:
--------------------------------------------------------------------------------
1 | $fields */
17 | private array $fields = [];
18 |
19 | /**
20 | * @param string $field
21 | * @return self
22 | */
23 | public function addField(string $field): self {
24 | $this->fields[] = $field;
25 | return $this;
26 | }
27 |
28 | /**
29 | * @return array
30 | */
31 | public function getFields(): array {
32 | return $this->fields;
33 | }
34 |
35 | /**
36 | * @param array $row
37 | * @return array>|false
38 | */
39 | public function check(array $row): array|false {
40 | $activeFilters = array_filter(
41 | $this->fields,
42 | // @phpstan-ignore-next-line
43 | fn ($filterField) => $row[$filterField]
44 | );
45 | if (sizeof($activeFilters) < 2) {
46 | return false;
47 | }
48 |
49 | return self::convertRowToSingleFilterOnes($row, $activeFilters);
50 | }
51 |
52 | /**
53 | * @param array $row
54 | * @param array $activeFilters
55 | * @return array>
56 | */
57 | private static function convertRowToSingleFilterOnes(array $row, array $activeFilters): array {
58 | $addRows = [];
59 | foreach ($activeFilters as $j => $filterField) {
60 | $newRow = $row;
61 | foreach ($activeFilters as $k => $filterField) {
62 | if ($j === $k) {
63 | continue;
64 | }
65 | $newRow[$filterField] = 0;
66 | }
67 | $addRows[] = $newRow;
68 | }
69 |
70 | return $addRows;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Response/DisabledMetricAdding.php:
--------------------------------------------------------------------------------
1 | $disabledMetricNodes */
24 | protected array $disabledMetricNodes = [];
25 |
26 | /**
27 | * @param array $metricNodes
28 | */
29 | public function __construct(protected array $metricNodes) {
30 | }
31 |
32 | /**
33 | * @return bool
34 | */
35 | public function isAvailable(): bool {
36 | $this->disabledMetricNodes = array_filter(
37 | $this->metricNodes,
38 | fn ($node) => $node->isDisabled()
39 | );
40 | return !!sizeof($this->disabledMetricNodes);
41 | }
42 |
43 | /**
44 | * @return static
45 | */
46 | public function apply(): static {
47 | $metricFields = array_map(
48 | fn ($node) => $node->getFieldAlias() ?: $node->getField(),
49 | $this->disabledMetricNodes
50 | );
51 | foreach (array_keys($this->responseRows) as $i) {
52 | foreach ($metricFields as $field) {
53 | $this->responseRows[$i][$field] = '';
54 | }
55 | }
56 |
57 | return $this;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Response/Executor.php:
--------------------------------------------------------------------------------
1 | $logics */
30 | protected array $logics;
31 |
32 | /** @var array> $responseRows */
33 | protected array $responseRows = [];
34 |
35 | /**
36 | * @param array> $responseRows
37 | * @return static
38 | */
39 | public function setResponseRows(array $responseRows): static {
40 | $this->responseRows = $responseRows;
41 | return $this;
42 | }
43 |
44 | /**
45 | * @return array>
46 | */
47 | public function getResponseRows(): array {
48 | return $this->responseRows;
49 | }
50 |
51 | /**
52 | * @return bool
53 | */
54 | public function execute(): bool {
55 | $availableLogics = array_filter(
56 | $this->logics,
57 | fn ($logic) => $logic->isAvailable()
58 | );
59 |
60 | foreach ($availableLogics as $logic) {
61 | $this->responseRows = $logic
62 | ->setResponseRows($this->responseRows)
63 | ->apply()
64 | ->getResponseRows();
65 | }
66 |
67 | return true;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Response/Interfaces/ResponseLogicInterface.php:
--------------------------------------------------------------------------------
1 | > $responseRows
23 | * @return static
24 | */
25 | public function setResponseRows(array $responseRows): static;
26 |
27 | /**
28 | * @return array>
29 | */
30 | public function getResponseRows(): array;
31 |
32 | /** @return bool */
33 | public function isAvailable(): bool;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Response/Sorting/Metric/CalculatorFactory.php:
--------------------------------------------------------------------------------
1 | countField);
33 | }
34 |
35 | /**
36 | * @return CountCalculator
37 | */
38 | public function create(): CountCalculator {
39 | return new CountCalculator();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Response/Sorting/Metric/CountCalculator.php:
--------------------------------------------------------------------------------
1 | > $sortRows
21 | * @param string $sortField
22 | * @return int|float|false
23 | */
24 | public function calc(array $sortRows, string $sortField): int|float|false {
25 | return array_sum(
26 | array_column($sortRows, $sortField)
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Response/Sorting/Metric/MetricCalculatorInterface.php:
--------------------------------------------------------------------------------
1 | > $sortRows
21 | * @param string $sortField
22 | * @return int|float|false
23 | */
24 | public function calc(array $sortRows, string $sortField): int|float|false;
25 | }
26 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Response/Sorting/Metric/MetricUpdaterInterface.php:
--------------------------------------------------------------------------------
1 | >
29 | */
30 | public function getUpdates(): array;
31 | }
32 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/Logic/Response/Sorting/Metric/Operation.php:
--------------------------------------------------------------------------------
1 | field;
29 | }
30 |
31 | /**
32 | * @param SphinxQLRequest $request
33 | * @return static
34 | */
35 | public function setRequest(SphinxQLRequest $request): static {
36 | parent::setRequest($request);
37 | $this->countField = $request->getCountField();
38 | return $this;
39 | }
40 |
41 | /**
42 | * @param array $responseNode
43 | * @param array $extraData
44 | * @return void
45 | */
46 | protected function makeResponseBucketsIfNotExist(array &$responseNode, array $extraData = []): void {
47 | if (array_key_exists($this->key, $responseNode)
48 | && (is_array($responseNode[$this->key]) && array_key_exists('buckets', $responseNode[$this->key]))
49 | ) {
50 | return;
51 | }
52 | if (!array_key_exists($this->key, $responseNode)) {
53 | $responseNode[$this->key] = [];
54 | }
55 | /** @var array $subNode */
56 | $subNode = &$responseNode[$this->key];
57 | $subNode['buckets'] = [];
58 | if (!$extraData) {
59 | return;
60 | }
61 | $responseNode[$this->key] += $extraData;
62 | }
63 |
64 | /**
65 | * @param array> $buckets
66 | * @param string $key
67 | * @param mixed $val
68 | * @return int
69 | */
70 | public function findBucket(array $buckets, string $key, mixed $val): int {
71 | foreach ($buckets as $i => $bucket) {
72 | if (array_key_exists($key, $bucket) && $bucket[$key] === $val) {
73 | return $i;
74 | }
75 | }
76 | return -1;
77 | }
78 |
79 | /**
80 | * @param array $responseNode
81 | * @param array $dataRow
82 | * @param string $nextNodeKey
83 | * @return array|false
84 | */
85 | abstract public function fillInResponse(array &$responseNode, array $dataRow, string $nextNodeKey): array|false;
86 | }
87 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/RequestNode/BaseNode.php:
--------------------------------------------------------------------------------
1 | key;
31 | }
32 |
33 | /**
34 | * @param SphinxQLRequest $request
35 | * @return static
36 | */
37 | public function setRequest(SphinxQLRequest $request): static {
38 | $this->request = $request;
39 | return $this;
40 | }
41 |
42 | /** @return void */
43 | public function disable(): void {
44 | $this->isDisabled = true;
45 | }
46 |
47 | /**
48 | * @return bool
49 | */
50 | public function isDisabled(): bool {
51 | return $this->isDisabled;
52 | }
53 |
54 | /**
55 | * @return void
56 | */
57 | abstract public function fillInRequest(): void;
58 | }
59 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/RequestNode/BaseRange.php:
--------------------------------------------------------------------------------
1 | $ranges */
26 | protected array $ranges;
27 |
28 | /**
29 | * @return int
30 | */
31 | public function getRangeCount(): int {
32 | return sizeof($this->ranges);
33 | }
34 |
35 | /**
36 | * @return void
37 | */
38 | protected function makeFieldExpr(): void {
39 | $rangeExprs = [];
40 | foreach ($this->ranges as $range) {
41 | $rangeExpr = '';
42 | if ($range['from']) {
43 | $rangeExpr = "range_from={$range['from']}";
44 | }
45 | if ($range['to']) {
46 | $rangeExpr .= ($rangeExpr ? ',' : '') . "range_to={$range['to']}";
47 | }
48 | if (in_array($rangeExpr, $rangeExprs)) {
49 | // There's no need to send the same range multiple times
50 | continue;
51 | }
52 | $rangeExprs[] = "{{$rangeExpr}}";
53 | }
54 | $this->fieldExpr = static::EXPR_FUNC . "({$this->argField}," . implode(',', $rangeExprs) . ')';
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/RequestNode/ExprNode.php:
--------------------------------------------------------------------------------
1 | fieldAlias;
33 | }
34 |
35 | /**
36 | * @param string $alias
37 | * @return void
38 | */
39 | public function setFieldAlias(string $alias): void {
40 | $this->fieldAlias = $alias;
41 | }
42 |
43 | /**
44 | * @return string
45 | */
46 | public function getField(): string {
47 | if (!$this->fieldExpr) {
48 | $this->makeFieldExpr();
49 | }
50 | return $this->fieldExpr;
51 | }
52 |
53 | /**
54 | * @return string
55 | */
56 | public function getArgField(): string {
57 | return $this->argField;
58 | }
59 |
60 | /**
61 | * @return void
62 | */
63 | public function fillInRequest(): void {
64 | if (!$this->fieldExpr) {
65 | $this->makeFieldExpr();
66 | }
67 | $this->request->addField($this->fieldExpr, $this->fieldAlias);
68 | }
69 |
70 | /**
71 | * @ return void
72 | */
73 | abstract protected function makeFieldExpr(): void;
74 | }
75 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/RequestNode/GroupExprNode.php:
--------------------------------------------------------------------------------
1 | groupField = $this->fieldAlias ?: $this->field;
28 | $this->request->addGroupField($this->groupField);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/RequestNode/Helpers/FilterExpression/Factory.php:
--------------------------------------------------------------------------------
1 | $nonAggFields
21 | */
22 | public function __construct(private array $nonAggFields) {
23 | }
24 |
25 | /**
26 | * @return FilterExpression
27 | */
28 | public function create() {
29 | return new FilterExpression($this->nonAggFields);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/RequestNode/Helpers/TimeZoneExpression.php:
--------------------------------------------------------------------------------
1 | getOffset(new \DateTime) / 3600;
28 |
29 | return '+' . ($offset < 10 ? '0' : '') . $offset . ':00';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/RequestNode/Interfaces/AggNodeInterface.php:
--------------------------------------------------------------------------------
1 | $responseNode
25 | * @param array $dataRow
26 | * @param string $nextNodeKey
27 | * @return array|false
28 | */
29 | public function fillInResponse(array &$responseNode, array $dataRow, string $nextNodeKey): array|false;
30 | }
31 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/RequestNode/Interfaces/AliasedNodeInterface.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | public function getFilter(): array;
28 | }
29 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/RequestNode/Metric.php:
--------------------------------------------------------------------------------
1 | name = $this->func = $nodeName;
39 | }
40 |
41 | /**
42 | * @return string
43 | */
44 | public function getName(): string {
45 | return $this->name;
46 | }
47 |
48 | /**
49 | * @return string
50 | */
51 | public function getFunc(): string {
52 | return $this->func;
53 | }
54 |
55 | /**
56 | * @return string
57 | */
58 | public function getArgField(): string {
59 | return $this->argField;
60 | }
61 |
62 | /**
63 | * @param array $responseNode
64 | * @param array $dataRow
65 | * @param string $nextNodeKey
66 | * @return array|false
67 | */
68 | public function fillInResponse(array &$responseNode, array $dataRow, string $nextNodeKey): array|false {
69 | /** @var array{value:string} $subNode */
70 | $subNode = &$responseNode[$this->key];
71 | if (array_key_exists($this->key, $dataRow)) {
72 | $subNode['value'] = $dataRow[$this->key];
73 | } else {
74 | $dataField = $this->fieldAlias ?: $this->getField();
75 | if (array_key_exists($dataField, $dataRow)) {
76 | $subNode['value'] = $dataRow[$dataField];
77 | }
78 | }
79 | return $nextNodeKey === '' ? [$nextNodeKey] : [];
80 | }
81 |
82 | /**
83 | * @ return void
84 | */
85 | protected function makeFieldExpr(): void {
86 | $this->fieldExpr = "{$this->func}({$this->argField})";
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/KibanaSearch/TableFieldInfo.php:
--------------------------------------------------------------------------------
1 | >> $fieldPerTableInfo */
24 | protected $fieldPerTableInfo = [];
25 | /** @var array> $fieldInfo */
26 | protected array $fieldInfo = [];
27 | /** @var array $tables */
28 | protected array $tables = [];
29 |
30 | /**
31 | * @param string $requestTable
32 | * @param Client $manticoreClient
33 | */
34 | public function __construct(protected string $requestTable, protected Client $manticoreClient) {
35 | $this->load();
36 | }
37 |
38 | /**
39 | * @return array
40 | */
41 | public function getTables(): array {
42 | return $this->tables;
43 | }
44 |
45 | /**
46 | * @return array>
47 | */
48 | public function get(): array {
49 | return $this->fieldInfo;
50 | }
51 |
52 | /**
53 | * @param string $table
54 | * @return array
55 | */
56 | public function getFieldNamesByTable(string $table): array {
57 | if (sizeof($this->tables) < 2) {
58 | return array_keys($this->fieldInfo);
59 | }
60 | if (!array_key_exists($table, $this->fieldPerTableInfo)) {
61 | $this->load($table);
62 | }
63 | return array_keys($this->fieldPerTableInfo[$table]);
64 | }
65 |
66 | /**
67 | * @return array
68 | */
69 | public function getFieldNames(): array {
70 | return array_keys($this->fieldInfo);
71 | }
72 |
73 | /**
74 | * @return void
75 | */
76 | protected function load(string $table = ''): void {
77 | $requestTable = $table ?: $this->requestTable;
78 | /** @var array{
79 | * fields:array>,
80 | * indices:array
81 | * } $requestTableInfo
82 | */
83 | $requestTableInfo = $this->manticoreClient->sendRequest(
84 | '{"fields":"*"}',
85 | $requestTable . static::REQUEST_TABLE_INFO_ENDPOINT,
86 | true
87 | )->getResult();
88 |
89 | if ($table) {
90 | $this->fieldPerTableInfo[$table] = $requestTableInfo['fields'];
91 | } else {
92 | $this->fieldInfo = $requestTableInfo['fields'];
93 | $this->tables = $requestTableInfo['indices'];
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/LicenseHandler.php:
--------------------------------------------------------------------------------
1 | [
45 | 'status' => 'active',
46 | 'uid' => 'no_license',
47 | 'type' => 'basic',
48 | 'issue_date' => '2023-01-01T00:00:000.000Z',
49 | 'issue_date_in_millis' => 0,
50 | 'max_nodes' => 1000,
51 | 'issued_to' => 'docker-cluster',
52 | 'issuer' => 'elasticsearch',
53 | 'start_date_in_millis' => -1,
54 | ],
55 | ]
56 | );
57 | };
58 |
59 | return Task::create($taskFn)->run();
60 | }
61 |
62 | /**
63 | * @return array
64 | */
65 | public function getProps(): array {
66 | return [];
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/ManagerSettingsKibanaHandler.php:
--------------------------------------------------------------------------------
1 | payload->path);
42 | }
43 |
44 | /**
45 | * @return array
46 | */
47 | public function getProps(): array {
48 | return [];
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/MetricKibanaHandler.php:
--------------------------------------------------------------------------------
1 | run();
49 | }
50 |
51 | /**
52 | * @return array
53 | */
54 | public function getProps(): array {
55 | return [];
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/QueryMapLoaderTrait.php:
--------------------------------------------------------------------------------
1 | > $queryMap */
21 | protected static $queryMap = [];
22 |
23 | /**
24 | * @param string $mapName
25 | * @return void
26 | */
27 | protected static function initQueryMap(string $mapName): void {
28 | if (isset(static::$queryMap[$mapName])) {
29 | return;
30 | }
31 | $queryMapFilePattern = __DIR__ . '/QueryMap/%MAP_NAME%.php';
32 | /** @var array $queryMap */
33 | $queryMap = include (string)str_replace('%MAP_NAME%', $mapName, $queryMapFilePattern);
34 | static::$queryMap[$mapName] = $queryMap;
35 | }
36 |
37 | /**
38 | * @param string $query
39 | * @param string $mapName
40 | * @param ?\Closure $preprocessor
41 | * @return Task
42 | * @throws RuntimeException
43 | */
44 | protected static function getResponseByQuery(string $mapName, string $query, ?\Closure $preprocessor = null): Task {
45 | if (!isset(self::$queryMap[$mapName])) {
46 | throw new \Exception("Unknown error on $mapName query map load");
47 | }
48 | if (!isset(self::$queryMap[$mapName][$query])) {
49 | throw new \Exception("Unknown request path passed: $query");
50 | }
51 |
52 | /** @var array $resp */
53 | $resp = self::$queryMap[$mapName][$query];
54 | if ($preprocessor !== null) {
55 | $preprocessor($resp);
56 | }
57 | $taskFn = static function (array $resp): TaskResult {
58 | return TaskResult::raw($resp);
59 | };
60 |
61 | return Task::create($taskFn, [$resp])->run();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/SettingsKibanaHandler.php:
--------------------------------------------------------------------------------
1 | payload->path);
42 | }
43 |
44 | /**
45 | * @return array
46 | */
47 | public function getProps(): array {
48 | return [];
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/Plugin/EmulateElastic/TelemetryKibanaHandler.php:
--------------------------------------------------------------------------------
1 | 1,
44 | '_seq_no' => 0,
45 | 'updated' => 1,
46 | '_id' => 'telemetry:telemetry',
47 | '_index' => '.kibana',
48 | '_source' => [
49 | 'references' => [],
50 | 'telemetry' => [
51 | 'userHasSeenNotice' => true,
52 | ],
53 | 'type' => 'telemetry',
54 | 'updated_at' => '2024-05-28T11:23:42.444Z',
55 | ],
56 | '_type' => '_doc',
57 | '_version' => 1,
58 | 'found' => true,
59 | ]
60 | );
61 | };
62 |
63 | return Task::create($taskFn)->run();
64 | }
65 |
66 | /**
67 | * @return array
68 | */
69 | public function getProps(): array {
70 | return [];
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/Plugin/Insert/Error/AutoSchemaDisabledError.php:
--------------------------------------------------------------------------------
1 | $cols
22 | */
23 | protected array $cols = [];
24 | /**
25 | * @var array $colTypes
26 | */
27 | protected array $colTypes = [];
28 | /**
29 | * @var array $rows
30 | */
31 | protected array $rows = [];
32 | /**
33 | * @var string $name
34 | */
35 | protected string $error = '';
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/Plugin/Insert/QueryParser/Datalim.php:
--------------------------------------------------------------------------------
1 | ,colTypes:array}
20 | */
21 | public function parse(string $query): array;
22 |
23 | /**
24 | * Checking for unescaped characters. Just as a test feature so far
25 | *
26 | * @param string|array $row
27 | * @param class-string $errorHandler
28 | * @return void
29 | */
30 | public static function checkUnescapedChars(mixed $row, string $errorHandler): void;
31 |
32 | /**
33 | * @param callable $checker
34 | * @param array $rowVals
35 | * @param array &$types
36 | * @param array $cols
37 | * @param class-string $errorHandler
38 | * @return void
39 | */
40 | public static function checkColTypesError(
41 | callable $checker,
42 | array $rowVals,
43 | array &$types,
44 | array $cols,
45 | string $errorHandler
46 | ): void;
47 | }
48 |
--------------------------------------------------------------------------------
/src/Plugin/Insert/QueryParser/JSONParser.php:
--------------------------------------------------------------------------------
1 | cols = $this->colTypes = [];
30 | $isNdJson = !Struct::isValid($query);
31 | if ($isNdJson) {
32 | // checking if query has ndjson format
33 | $queries = static::parseNdJSON($query);
34 | foreach ($queries as $query) {
35 | $struct = Struct::fromJson($query);
36 | $row = $struct->toArray();
37 | if (!$row || !is_array($row)) {
38 | throw new QueryParseError('Invalid JSON in query');
39 | }
40 | $this->isNdJSON = true;
41 | $this->parseJSONRow($row);
42 | }
43 | } else {
44 | $struct = Struct::fromJson($query);
45 | $row = $struct->toArray();
46 | $this->parseJSONRow($row);
47 | }
48 |
49 | if ($this->error !== '') {
50 | throw new QueryParseError($this->error);
51 | }
52 | return ['name' => $this->name];
53 | }
54 |
55 | /**
56 | * @param string $query
57 | * @return Iterable
58 | */
59 | public static function parseNdJSON($query): Iterable {
60 | do {
61 | $eolPos = strpos($query, PHP_EOL);
62 | if ($eolPos === false) {
63 | $eolPos = strlen($query);
64 | }
65 | $row = substr($query, 0, $eolPos);
66 | if ($row !== '') {
67 | yield $row;
68 | }
69 | $query = substr($query, $eolPos + 1);
70 | } while (strlen($query) > 0);
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/Plugin/Insert/QueryParser/JSONParserInterface.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | public static function parseNdJSON(string $query): Iterable;
20 | /**
21 | * @param array $query
22 | * @return array
23 | */
24 | public function parseJSONRow(array $query): array;
25 | }
26 |
--------------------------------------------------------------------------------
/src/Plugin/Insert/QueryParser/Loader.php:
--------------------------------------------------------------------------------
1 | value !== $requestPath);
28 | }
29 |
30 | /**
31 | * @param string $reqPath
32 | * @param ManticoreEndpoint $reqEndpointBundle
33 | * @return InsertQueryParserInterface
34 | */
35 | public static function getInsertQueryParser(
36 | string $reqPath,
37 | ManticoreEndpoint $reqEndpointBundle
38 | ): InsertQueryParserInterface {
39 | // Resolve the possible ambiguity with Manticore query format as it may not correspond to request format
40 | $reqFormat = match ($reqEndpointBundle) {
41 | ManticoreEndpoint::Cli, ManticoreEndpoint::CliJson, ManticoreEndpoint::Sql => RequestFormat::SQL,
42 | ManticoreEndpoint::Insert, ManticoreEndpoint::Replace, ManticoreEndpoint::Bulk => RequestFormat::JSON,
43 | default => throw new ParserLoadError("Unsupported endpoint bundle '{$reqEndpointBundle->value}' passed"),
44 | };
45 | $parserClass = match ($reqFormat) {
46 | RequestFormat::SQL => 'SQLInsertParser',
47 | RequestFormat::JSON => self::isElasticLikeRequest($reqPath, $reqEndpointBundle)
48 | ? 'ElasticJSONInsertParser'
49 | : 'JSONInsertParser',
50 | };
51 | $parserClassFull = __NAMESPACE__ . '\\' . $parserClass;
52 | $parser = ($parserClassFull === __NAMESPACE__ . '\ElasticJSONInsertParser')
53 | ? new $parserClassFull($reqPath)
54 | : new $parserClassFull();
55 | if ($parser instanceof InsertQueryParserInterface) {
56 | return $parser;
57 | }
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/Plugin/Insert/QueryParser/QueryParserInterface.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | public function parse(string $query): array;
20 | }
21 |
--------------------------------------------------------------------------------
/src/Plugin/Metrics/Payload.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | final class Payload extends BasePayload
23 | {
24 | public string $path;
25 |
26 | public string $table;
27 |
28 |
29 | /**
30 | * Get description for this plugin
31 | * @return string
32 | */
33 | public static function getInfo(): string {
34 | return 'Returns Prometheus metrics';
35 | }
36 |
37 | /**
38 | * @param Request $request
39 | * @return static
40 | */
41 | public static function fromRequest(Request $request): static {
42 | $self = new static();
43 | $self->path = $request->path;
44 | return $self;
45 | }
46 |
47 | /**
48 | * @param Request $request
49 | * @return bool
50 | */
51 | public static function hasMatch(Request $request): bool {
52 | return $request->endpointBundle->value === 'metrics';
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Plugin/ModifyTable/Handler.php:
--------------------------------------------------------------------------------
1 | type} table {$payload->table} {$payload->structure} {$payload->extra}";
38 | $resp = $client->sendRequest($q, disableAgentHeader: true);
39 | return TaskResult::fromResponse($resp);
40 | };
41 |
42 | $task = Task::create(
43 | $taskFn,
44 | [$this->payload, $this->manticoreClient]
45 | );
46 |
47 | return $task->run();
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/Plugin/Plugin/ActionType.php:
--------------------------------------------------------------------------------
1 | $payload
29 | * @return void
30 | */
31 | public function __construct(public Payload $payload) {
32 | }
33 |
34 | /**
35 | * Process the request
36 | * @return Task
37 | */
38 | public function run(): Task {
39 |
40 | $name = $this->getName($this->payload);
41 | $tableName = $this->getTableName();
42 |
43 | /**
44 | * @param string $name
45 | * @param string $tableName
46 | * @return TaskResult
47 | */
48 | $taskFn = function (string $name, string $tableName): TaskResult {
49 | $manticoreClient = $this->manticoreClient;
50 | if (!$manticoreClient->hasTable($tableName)) {
51 | return TaskResult::none();
52 | }
53 |
54 | return TaskResult::withTotal($this->processDrop($name, $tableName));
55 | };
56 |
57 | return Task::create(
58 | $taskFn,
59 | [$name, $tableName]
60 | )->run();
61 | }
62 |
63 | /**
64 | * @param string $name
65 | * @param string $tableName
66 | * @return int
67 | */
68 | abstract protected function processDrop(string $name, string $tableName): int;
69 |
70 | /**
71 | * @param Payload $payload
72 | * @return string
73 | */
74 | abstract protected function getName(Payload $payload): string;
75 |
76 | /**
77 | * @return string
78 | */
79 | abstract protected function getTableName(): string;
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Handlers/BaseViewHandler.php:
--------------------------------------------------------------------------------
1 | $payload
31 | * @return void
32 | */
33 | public function __construct(public Payload $payload) {
34 | }
35 |
36 |
37 | /**
38 | * Process the request
39 | * @return Task
40 | */
41 | public function run(): Task {
42 |
43 | $tableName = $this->getTableName();
44 | /**
45 | * @param string $tableName
46 | * @param Client $manticoreClient
47 | * @return TaskResult
48 | * @throws ManticoreSearchClientError
49 | */
50 | $taskFn = static function (string $tableName, Client $manticoreClient): TaskResult {
51 |
52 |
53 | if (!$manticoreClient->hasTable($tableName)) {
54 | return TaskResult::none();
55 | }
56 |
57 | $sql = /** @lang manticore */
58 | "SELECT name FROM $tableName GROUP BY name";
59 | $resp = $manticoreClient->sendRequest($sql);
60 | if ($resp->hasError()) {
61 | throw ManticoreSearchClientError::create((string)$resp->getError());
62 | }
63 |
64 | return TaskResult::fromResponse($resp);
65 | };
66 |
67 | return Task::create(
68 | $taskFn,
69 | [$tableName, $this->manticoreClient]
70 | )->run();
71 | }
72 |
73 | abstract protected function getTableName(): string;
74 | }
75 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Handlers/Source/BaseCreateSourceHandler.php:
--------------------------------------------------------------------------------
1 | $payload
31 | * @return void
32 | */
33 | public function __construct(public Payload $payload) {
34 | }
35 |
36 | /**
37 | * Process the request
38 | * @return Task
39 | */
40 | public function run(): Task {
41 | /**
42 | * @param Payload $payload
43 | * @param Client $manticoreClient
44 | * @return TaskResult
45 | * @throws ManticoreSearchClientError
46 | */
47 | $taskFn = static function (Payload $payload, Client $manticoreClient): TaskResult {
48 |
49 | self::checkAndCreateSource($manticoreClient);
50 | return static::handle($payload, $manticoreClient);
51 | };
52 |
53 | return Task::create(
54 | $taskFn,
55 | [$this->payload, $this->manticoreClient]
56 | )->run();
57 | }
58 |
59 | /**
60 | * @throws ManticoreSearchClientError
61 | */
62 | protected static function checkAndCreateSource(Client $manticoreClient): void {
63 | if ($manticoreClient->hasTable(Payload::SOURCE_TABLE_NAME)) {
64 | return;
65 | }
66 |
67 | $sql = /** @lang ManticoreSearch */
68 | 'CREATE TABLE ' . Payload::SOURCE_TABLE_NAME .
69 | ' (id bigint, type text, name text attribute indexed, '.
70 | 'full_name text, buffer_table text, attrs json, custom_mapping json, original_query text)';
71 |
72 | $request = $manticoreClient->sendRequest($sql);
73 | if ($request->hasError()) {
74 | throw ManticoreSearchClientError::create((string)$request->getError());
75 | }
76 | }
77 |
78 | /**
79 | * @param Payload $payload
80 | * @param Client $manticoreClient
81 | * @return TaskResult
82 | */
83 | abstract public static function handle(Payload $payload, Client $manticoreClient): TaskResult;
84 | }
85 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Handlers/Source/ViewSourceHandler.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | final class ViewSourceHandler extends BaseViewHandler {
28 | protected function getTableName(): string {
29 | return Payload::SOURCE_TABLE_NAME;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Handlers/View/GetViewHandler.php:
--------------------------------------------------------------------------------
1 |
34 | * },
35 | * base_expr: string
36 | * }
37 | * }
38 | * }>
39 | */
40 | final class GetViewHandler extends BaseGetHandler {
41 |
42 | /**
43 | * @param Payload
59 | * },
60 | * base_expr: string
61 | * }
62 | * }
63 | * }> $payload
64 | * @return string
65 | */
66 | protected function getName(Payload $payload): string {
67 | $parsedPayload = $payload->model->getPayload();
68 | return $parsedPayload['SHOW'][2]['no_quotes']['parts'][0];
69 | }
70 |
71 | protected static function formatResult(string $query): string {
72 | return str_replace("\n", '', $query);
73 | }
74 |
75 | protected function getType(): string {
76 | return 'View';
77 | }
78 |
79 | protected function getTableName(): string {
80 | return Payload::VIEWS_TABLE_NAME;
81 | }
82 |
83 | protected function getFields(): array {
84 | return ['suspended'];
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Handlers/View/ViewViewsHandler.php:
--------------------------------------------------------------------------------
1 |
30 | */
31 | final class ViewViewsHandler extends BaseViewHandler {
32 |
33 |
34 | protected function getTableName(): string {
35 | return Payload::VIEWS_TABLE_NAME;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Alter/AlterMaterializedViewModel.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class AlterMaterializedViewModel extends Model {
22 |
23 | public function getHandlerClass(): string {
24 | return 'Handlers\\View\\AlterViewHandler';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Create/CreateMaterializedViewModel.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class CreateMaterializedViewModel extends Model {
22 |
23 |
24 | public function getHandlerClass(): string {
25 | return 'Handlers\\View\\CreateViewHandler';
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Create/CreateSourceModel.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class CreateSourceModel extends Model {
23 |
24 | /**
25 | * @throws GenericError
26 | */
27 | public function getHandlerClass(): string {
28 | return $this->parseSourceType();
29 | }
30 |
31 |
32 | /**
33 | * @throws GenericError
34 | */
35 | public function parseSourceType(): string {
36 | foreach ($this->getPayload()['SOURCE']['options'] as $option) {
37 | if (isset($option['sub_tree'][0]['base_expr'])
38 | && $option['sub_tree'][0]['base_expr'] === 'type') {
39 | return match (SqlQueryParser::removeQuotes($option['sub_tree'][2]['base_expr'])) {
40 | 'kafka' => 'Handlers\\Source\\CreateKafka',
41 | default => throw new GenericError('Cannot find handler for request type: ' . static::class)
42 | };
43 | }
44 | }
45 | throw new GenericError('Cannot find handler for request type: ' . static::class);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Drop/DropMaterializedViewModel.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class DropMaterializedViewModel extends Model {
21 |
22 | public function getHandlerClass(): string {
23 | return 'Handlers\\View\\DropViewHandler';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Drop/DropSourceModel.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class DropSourceModel extends Model {
21 |
22 | public function getHandlerClass(): string {
23 | return 'Handlers\\Source\\DropSourceHandler';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Factories/AlterFactory.php:
--------------------------------------------------------------------------------
1 | |null
24 | */
25 | public static function create(array $parsedPayload): ?Model {
26 |
27 | $model = null;
28 |
29 | if (self::isAlterMaterializedViewMatch($parsedPayload)) {
30 | $model = new AlterMaterializedViewModel($parsedPayload);
31 | }
32 |
33 | /** @var Model|null $model */
34 | return $model;
35 | }
36 |
37 |
38 | /**
39 | * Should match ALTER MATERIALIZED VIEW {name} suspended=0;
40 | *
41 | * @param array{
42 | * ALTER?: array{
43 | * base_expr: string,
44 | * sub_tree: mixed[]
45 | * },
46 | * VIEW?: array{
47 | * base_expr: string,
48 | * name: string,
49 | * no_quotes: array{
50 | * delim: bool,
51 | * parts: string[]
52 | * },
53 | * create-def: bool,
54 | * options: array{
55 | * expr_type: string,
56 | * base_expr: string,
57 | * delim: string,
58 | * sub_tree: array{
59 | * expr_type: string,
60 | * base_expr: string,
61 | * delim: string,
62 | * sub_tree: array{
63 | * expr_type: string,
64 | * base_expr: string
65 | * }[]
66 | * }[]
67 | * }[]
68 | * }
69 | * } $parsedPayload
70 | * @return bool
71 | *
72 | */
73 | private static function isAlterMaterializedViewMatch(array $parsedPayload): bool {
74 | return (
75 | isset($parsedPayload['ALTER']['base_expr']) &&
76 | !empty($parsedPayload['VIEW']['no_quotes']['parts']) &&
77 | !empty($parsedPayload['VIEW']['options']) &&
78 | strtolower($parsedPayload['ALTER']['base_expr']) === Payload::TYPE_MATERLIALIZED . ' ' . Payload::TYPE_VIEW
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Model.php:
--------------------------------------------------------------------------------
1 | parsedPayload = $parsedPayload;
29 | }
30 |
31 | /**
32 | * @phpstan-return T array
33 | */
34 | final public function getPayload(): array {
35 | return $this->parsedPayload;
36 | }
37 |
38 | abstract public function getHandlerClass(): string;
39 | }
40 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Show/ShowMaterializedViewModel.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class ShowMaterializedViewModel extends Model {
22 |
23 | public function getHandlerClass(): string {
24 | return 'Handlers\\View\\GetViewHandler';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Show/ShowMaterializedViewsModel.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class ShowMaterializedViewsModel extends Model {
21 |
22 | public function getHandlerClass(): string {
23 | return 'Handlers\\View\\ViewViewsHandler';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Show/ShowSourceModel.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class ShowSourceModel extends Model {
22 |
23 | public function getHandlerClass(): string {
24 | return 'Handlers\\Source\\GetSourceHandler';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/Show/ShowSourcesModel.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class ShowSourcesModel extends Model {
22 |
23 | public function getHandlerClass(): string {
24 | return 'Handlers\\Source\\ViewSourceHandler';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Models/SqlModelsHandler.php:
--------------------------------------------------------------------------------
1 | |null
25 | */
26 | public static function handle(?array $parsed): ?Model {
27 |
28 | // Order here is important !!!
29 | if (isset($parsed['ALTER'])) {
30 | /** @var Model|null $result */
31 | $result = AlterFactory::create($parsed);
32 | return $result;
33 | }
34 |
35 | if (isset($parsed['CREATE'])) {
36 | /** @var Model|null $result */
37 | $result = CreateFactory::create($parsed);
38 | return $result;
39 | }
40 |
41 | if (isset($parsed['DROP'])) {
42 | /** @var Model|null $result */
43 | $result = DropFactory::create($parsed);
44 | return $result;
45 | }
46 |
47 | if (isset($parsed['SHOW'])) {
48 | /** @var Model|null $result */
49 | $result = ShowFactory::create($parsed);
50 | return $result;
51 | }
52 |
53 | return null;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Plugin/Queue/Workers/Kafka/Batch.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | private array $batch = [];
23 |
24 | /**
25 | * @var Closure(array): bool $closure
26 | */
27 | private Closure $callback;
28 |
29 | private int $lastCallTime = 0;
30 |
31 | public function __construct(int $batchSize = 50) {
32 | $this->setBatchSize($batchSize);
33 | }
34 |
35 | public function setBatchSize(int $batchSize): void {
36 | $this->batchSize = $batchSize;
37 | }
38 |
39 | /**
40 | * @param Closure(array): bool $closure
41 | * @return void
42 | */
43 | public function setCallback(Closure $closure): void {
44 | $this->callback = $closure;
45 | }
46 |
47 | public function checkProcessingTimeout(): bool {
48 | return $this->lastCallTime + 10 < time();
49 | }
50 |
51 | public function add(mixed $item): bool {
52 | $this->lastCallTime = time();
53 | $this->batch[] = $item;
54 |
55 | if (sizeof($this->batch) < $this->batchSize) {
56 | return false;
57 | }
58 | return $this->process();
59 | }
60 |
61 | public function process(): bool {
62 | if (empty($this->batch)) {
63 | return false;
64 | }
65 | $run = call_user_func($this->callback, $this->batch);
66 | $this->batch = [];
67 | return $run;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Plugin/Sharding/DescHandler.php:
--------------------------------------------------------------------------------
1 | payload->table;
38 | // TODO: think about the way to refactor it and remove duplication
39 | $q = "DESC {$table} OPTION force=1";
40 | $resp = $this->manticoreClient->sendRequest($q);
41 | /** @var array{0:array{data:array}} $result */
42 | $result = $resp->getResult();
43 | $shard = null;
44 | foreach ($result[0]['data'] as $row) {
45 | if ($row['Type'] === 'local') {
46 | $shard = $row['Agent'];
47 | break;
48 | }
49 | }
50 | if (!isset($shard)) {
51 | return TaskResult::withError('Failed to find structure from local shards');
52 | }
53 |
54 | $q = match ($this->payload->type) {
55 | 'show' => "SHOW CREATE TABLE {$shard}",
56 | 'desc', 'describe' => "DESC {$shard}",
57 | default => throw new RuntimeException("Unknown type: {$this->payload->type}"),
58 | };
59 | $resp = $this->manticoreClient->sendRequest($q);
60 | return TaskResult::fromResponse($resp);
61 | };
62 | $task = Task::create($taskFn, []);
63 | return $task->run();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Plugin/Sharding/TableOperation.php:
--------------------------------------------------------------------------------
1 | table} OPTION force=1";
48 | $resp = $manticoreClient->sendRequest($query);
49 |
50 | // It's important to have ` and 2 spaces for Apache Superset
51 | $resp->mapData(
52 | static function (array $row): array {
53 | /** @var array{'Create Table':string} $row */
54 | $lines = explode("\n", $row['Create Table']);
55 | $lastN = sizeof($lines) - 1;
56 | foreach ($lines as $n => &$line) {
57 | if ($n === 0 || $n === $lastN) {
58 | continue;
59 | }
60 | $parts = explode(' ', $line);
61 | $parts[0] = '`' . trim($parts[0], '`') . '`';
62 | $line = ' ' . trim(implode(' ', $parts));
63 | }
64 | $row['Create Table'] = implode("\n", $lines);
65 | return $row;
66 | }
67 | );
68 |
69 | return TaskResult::fromResponse($resp);
70 | };
71 |
72 | return Task::create(
73 | $taskFn,
74 | [$this->payload, $this->manticoreClient]
75 | )->run();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Plugin/Show/SchemasHandler.php:
--------------------------------------------------------------------------------
1 | }} */
49 | $result = $manticoreClient->sendRequest($query)->getResult();
50 | return TaskResult::withData($result[0]['data'])
51 | ->column('Database', Column::String);
52 | };
53 |
54 | return Task::create(
55 | $taskFn,
56 | [$this->manticoreClient]
57 | )->run();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Plugin/Show/VersionHandler.php:
--------------------------------------------------------------------------------
1 | sendRequest($query)->getResult()))
44 | ->column('Component', Column::String)
45 | ->column('Version', Column::String);
46 | };
47 |
48 | return Task::create(
49 | $taskFn, [$this->manticoreClient]
50 | )->run();
51 | }
52 |
53 | /**
54 | * @param Struct $result
55 | * @return array, array>
56 | */
57 | private static function parseVersions(Struct $result):array {
58 | $versions = [];
59 | if (is_array($result[0]) && isset($result[0]['data'][0]['Value'])) {
60 | $value = $result[0]['data'][0]['Value'];
61 |
62 | $splittedVersions = explode('(', $value);
63 |
64 | foreach ($splittedVersions as $n => $version) {
65 | $version = trim($version);
66 |
67 | if ($version[mb_strlen($version) - 1] === ')') {
68 | $version = substr($version, 0, -1);
69 | }
70 |
71 | $exploded = explode(' ', $version);
72 | $component = $n > 0 ? ucfirst($exploded[0]) : 'Daemon';
73 |
74 | $versions[] = ['Component' => $component, 'Version' => $version];
75 | }
76 | }
77 |
78 | return $versions;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Plugin/Test/Handler.php:
--------------------------------------------------------------------------------
1 | 0) {
47 | Coroutine::sleep($timeout);
48 | }
49 |
50 | return TaskResult::none();
51 | };
52 |
53 | $task = Task::create($taskFn, [$this->payload->timeout]);
54 | if ($this->payload->isDeferred) {
55 | $task->defer();
56 | }
57 | return $task->run();
58 | }
59 |
60 | /**
61 | * @return array
62 | */
63 | public function getProps(): array {
64 | return [];
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Plugin/Test/Payload.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | final class Payload extends BasePayload {
22 | public function __construct(public int $timeout = 0, public bool $isDeferred = false) {
23 | }
24 |
25 | /**
26 | * Get description for this plugin
27 | * @return string
28 | */
29 | public static function getInfo(): string {
30 | return 'Test plugin, used exclusively for tests';
31 | }
32 |
33 | /**
34 | * @param Request $request
35 | * @return static
36 | */
37 | public static function fromRequest(Request $request): static {
38 | // Request for Test command emulating hung Buddy requests
39 | // Contains info on request timeout and the type of the command's Task(deferred or not)
40 | // E.g.: test 6/deferred ; test 10 ; test deferred
41 | $self = new static();
42 | $matches = [];
43 | preg_match('/^\s*test\s+(\d+)?\/?(deferred)?\s*$/i', $request->payload, $matches);
44 | $self->timeout = isset($matches[1]) ? abs((int)$matches[1]) : 0;
45 | $self->isDeferred = isset($matches[2]);
46 |
47 | return $self;
48 | }
49 |
50 | /**
51 | * @param Request $request
52 | * @return bool
53 | */
54 | public static function hasMatch(Request $request): bool {
55 | return stripos($request->payload, 'test') === 0;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Plugin/Truncate/Handler.php:
--------------------------------------------------------------------------------
1 | manticoreClient->hasTable($this->payload->table);
39 | if (!$tableExists) {
40 | throw GenericError::create(
41 | "Table {$this->payload->table} does not exist"
42 | );
43 | }
44 |
45 | $shards = $this->manticoreClient->getTableShards($this->payload->table);
46 | $requests = [];
47 | foreach ($shards as $shard) {
48 | $requests[] = [
49 | 'url' => $shard['url'],
50 | 'path' => 'sql?mode=raw',
51 | 'request' => "TRUNCATE TABLE {$shard['name']}",
52 | ];
53 | }
54 |
55 | $this->manticoreClient->sendMultiRequest($requests);
56 | return TaskResult::none();
57 | };
58 |
59 | return Task::create($taskFn)->run();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Plugin/Truncate/Payload.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | final class Payload extends BasePayload
24 | {
25 | public string $table;
26 |
27 | /**
28 | * Get description for this plugin
29 | * @return string
30 | */
31 | public static function getInfo(): string {
32 | return 'Handles TRUNCATE statements on distributed tables';
33 | }
34 |
35 | /**
36 | * @param Request $request
37 | * @return static
38 | */
39 | public static function fromRequest(Request $request): static {
40 | $self = new static();
41 | // Match truncate table pattern in the payload
42 | if (preg_match('/truncate\s+table\s+(?:`?([^`\s]+)`?)/i', $request->payload, $matches)) {
43 | // Extract table name from matches
44 | $self->table = $matches[1];
45 | return $self;
46 | }
47 |
48 | throw QueryParseError::create('Failed to handle your TRUNCATE query', true);
49 | }
50 |
51 | /**
52 | * @param Request $request
53 | * @return bool
54 | */
55 | public static function hasMatch(Request $request): bool {
56 | return $request->command === 'truncate' &&
57 | stripos($request->error, 'requires an existing RT table') !== false;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Plugin/Update/Handler.php:
--------------------------------------------------------------------------------
1 | table} SET {$payload->setExpr} WHERE {$payload->whereExpr}";
41 | $result = $client->sendRequest($stmt, path: null, disableAgentHeader: true);
42 | if ($result->hasError()) {
43 | throw GenericError::create(
44 | "Can't update table {$payload->table}" .
45 | 'Reason: ' . $result->getError()
46 | );
47 | }
48 |
49 | return TaskResult::none();
50 | };
51 |
52 | return Task::create(
53 | $taskFn, [$this->payload, $this->manticoreClient]
54 | )->run();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/func.php:
--------------------------------------------------------------------------------
1 | execute('add', [$name, $value]);
32 | }
33 |
34 | /**
35 | * Little helper to check if we have telemetry enabled
36 | *
37 | * @return bool
38 | */
39 | function is_telemetry_enabled(): bool {
40 | return ConfigManager::get('TELEMETRY', '1') === '1';
41 | }
42 |
43 | /**
44 | * Little helper to convert config into int
45 | * @param string $val
46 | * @return int
47 | */
48 | function return_bytes(string $val): int {
49 | $val = trim($val);
50 | $last = strtolower($val[strlen($val) - 1]);
51 | return (int)$val * match ($last) {
52 | 'g' => 1024 * 1024 * 1024,
53 | 'm' => 1024 * 1024,
54 | 'k' => 1024,
55 | default => 1,
56 | };
57 | }
58 |
59 |
60 | /**
61 | * @param int $errno
62 | * @param string $errstr
63 | * @param string $errfile
64 | * @param int $errline
65 | * @return void
66 | */
67 | function buddy_error_handler(int $errno, string $errstr, string $errfile, int $errline): void {
68 | throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
69 | }
70 |
71 | /**
72 | * Crossplatform absolute path to project root of the Buddy sources
73 | * when running as Phar and not as Phar
74 | * @return string|bool
75 | */
76 | function buddy_project_root(): string|bool {
77 | $projectRoot = class_exists('Phar') ? Phar::running(false) : false;
78 | if ($projectRoot) {
79 | $projectRoot = "phar://$projectRoot";
80 | } else {
81 | $projectRoot = realpath(
82 | __DIR__ . DIRECTORY_SEPARATOR
83 | . '..'
84 | );
85 | }
86 |
87 | return $projectRoot;
88 | }
89 |
--------------------------------------------------------------------------------
/test/Buddy/functional/AutoSchemaSupportTest.php:
--------------------------------------------------------------------------------
1 | testTable}");
26 | }
27 |
28 | protected function tearDown(): void {
29 | static::$manticoreConf = '';
30 | }
31 |
32 | /**
33 | * Helper to setup auto schema
34 | * @param int $value can be 0 or 1
35 | * @return void
36 | */
37 | protected function setUpAutoSchema(int $value): void {
38 | // Adding the auto schema option to manticore config
39 | $conf = str_replace(
40 | 'searchd {' . PHP_EOL,
41 | 'searchd {' . PHP_EOL . " auto_schema = $value" . PHP_EOL,
42 | static::$manticoreConf
43 | );
44 | self::updateManticoreConf((string)$conf);
45 | echo $conf . PHP_EOL;
46 |
47 | // Restart manticore
48 | static::tearDownAfterClass();
49 | sleep(5); // <- give 5 secs to protect from any kind of lags
50 | static::setUpBeforeClass();
51 | }
52 |
53 | public function testAutoSchemaOptionDisabled(): void {
54 | echo "\nTesting the fail on the execution of HTTP insert query with searchd auto_schema=0\n";
55 | $this->setUpAutoSchema(0);
56 | $query = "INSERT into {$this->testTable}(col1) VALUES(1) ";
57 | $out = static::runHttpQuery($query);
58 | $result = ['error' => "table 'test' absent"];
59 | $this->assertEquals($result, $out);
60 | }
61 |
62 | public function testAutoSchemaOptionEnabled(): void {
63 | echo "\nTesting the fail on the execution of HTTP insert query with searchd auto_schema=1\n";
64 | $this->setUpAutoSchema(1);
65 | $query = "INSERT into {$this->testTable}(col1) VALUES(1) ";
66 | $out = static::runHttpQuery($query);
67 | $result = [['total' => 1, 'error' => '','warning' => '']];
68 | $this->assertEquals($result, $out);
69 | }
70 |
71 | public function testAutoSchemaOptionOmitted(): void {
72 | echo "\nTesting the fail on the execution of HTTP insert query without searchd auto_schema set\n";
73 | $query = "INSERT into {$this->testTable}(col1,col2) VALUES(1,2) ";
74 | $out = static::runHttpQuery($query);
75 | $result = [['total' => 1,'error' => '','warning' => '']];
76 | $this->assertEquals($result, $out);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/test/Buddy/functional/BackupTest.php:
--------------------------------------------------------------------------------
1 | assertQueryResultContainsError('backup', 'You have an error in your query. Please, double check it.');
21 | $this->assertQueryResultContainsError('backup to /tmp', 'You have no tables to backup.');
22 | $this->assertQueryResultContainsError(
23 | 'backup tables a to /unexisting/dir',
24 | 'Backup directory is not writable'
25 | );
26 | $this->assertQueryResultContainsError('backup table c to /tmp', "Can't find some of the tables: c");
27 | }
28 |
29 | public function testBackupWorksWell(): void {
30 | // Prepare some tables first
31 | static::runSqlQuery('create table a');
32 | static::runSqlQuery('create table b');
33 |
34 | exec('rm -fr /tmp/backup1 /tmp/backup2 /tmp/backup3');
35 | exec('mkdir -p /tmp/backup1 /tmp/backup2 /tmp/backup3');
36 |
37 | $this->assertQueryResult('backup to /tmp/backup1', 'Path: /tmp/backup1/backup-');
38 | $this->assertQueryResult('backup tables a, b to /tmp/backup2', 'Path: /tmp/backup2/backup-');
39 | $this->assertQueryResult('backup table a to /tmp/backup3', 'Path: /tmp/backup3/backup-');
40 |
41 | static::runSqlQuery('drop table if exists a');
42 | static::runSqlQuery('drop table if exists b');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/test/Buddy/functional/BuildTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(true, file_exists('build/share/modules/manticore-buddy/src/main.php'));
22 | $this->assertEquals(true, file_exists('build/manticore-buddy'));
23 | }
24 |
25 | public function testBuildHasRightComposerPackages(): void {
26 | /** @var array{require:array,require-dev:array} $composer */
27 | $composer = simdjson_decode((string)file_get_contents('/workdir/composer.json'), true);
28 | $include = array_keys($composer['require']);
29 | $exclude = array_keys($composer['require-dev']);
30 | $vendorPath = 'build/share/modules/manticore-buddy/vendor';
31 | /** @var array{dev:bool,dev-package-names:array} $installed */
32 | $installed = simdjson_decode(
33 | (string)file_get_contents("$vendorPath/composer/installed.json"),
34 | true
35 | );
36 | $this->assertEquals(false, $installed['dev']);
37 | $this->assertEquals([], $installed['dev-package-names']);
38 |
39 |
40 | $vendorPathIterator = new RecursiveDirectoryIterator($vendorPath);
41 | $vendorPathLen = strlen($vendorPath);
42 | $packages = [];
43 | /** @var SplFileInfo $file */
44 | foreach (new RecursiveIteratorIterator($vendorPathIterator) as $file) {
45 | $ns = strtok(substr((string)$file, $vendorPathLen), '/');
46 | $name = strtok('/');
47 | $packages["$ns/$name"] = true;
48 | }
49 |
50 | $packages = array_keys($packages);
51 | $this->assertEquals([], array_diff($include, $packages));
52 | $this->assertEquals($exclude, array_diff($exclude, $packages));
53 | }
54 |
55 | protected static function buildBinary(): void {
56 | system('phar_builder/bin/build --name="Manticore Buddy" --package="manticore-buddy"');
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/test/Buddy/functional/DebugModeTest.php:
--------------------------------------------------------------------------------
1 | searchdLogFilepath = $matches[1];
35 | $this->searchdLog = (string)file_get_contents($this->searchdLogFilepath);
36 | self::setUpBeforeClass();
37 | }
38 |
39 | public function tearDown(): void {
40 | self::tearDownAfterClass();
41 | }
42 |
43 | public function testDebugModeOff(): void {
44 | echo "\nTesting the Buddy log output without debug mode enabled\n";
45 | ob_flush();
46 | // Waiting for the possible debug message to come
47 | sleep(70);
48 | // Checking the log part corresponding to the latest searchd start
49 | $logUpdate = str_replace($this->searchdLog, '', (string)file_get_contents($this->searchdLogFilepath));
50 | $this->assertStringNotContainsString('[BUDDY] memory usage:', $logUpdate);
51 | static::setSearchdArgs(['--log-level=debugv']);
52 | }
53 |
54 | /**
55 | * @depends testDebugModeOff
56 | */
57 | public function testDebugModeOn(): void {
58 | echo "\nTesting the Buddy log output with debug mode enabled\n";
59 | ob_flush();
60 | // Waiting for the possible debug message to come
61 | sleep(70);
62 | // Checking the log part corresponding to the latest searchd start
63 | $logUpdate = str_replace($this->searchdLog, '', (string)file_get_contents($this->searchdLogFilepath));
64 | $this->assertStringContainsString('[BUDDY] memory usage:', $logUpdate);
65 | static::setSearchdArgs([]);
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/test/Buddy/functional/ListenArgTest.php:
--------------------------------------------------------------------------------
1 | defaultPort = $this->getListenDefaultPort();
26 | }
27 |
28 | protected function tearDown(): void {
29 | // Restoring listen default port
30 | $this->setListenDefaultPort($this->defaultPort);
31 | }
32 |
33 | public function testListenArgumentChange(): void {
34 | echo "\nTesting if the `listen` argument is passed from daemon to Buddy correctly\n";
35 | $this->setListenDefaultPort(8888);
36 | $httpPort = self::getListenHttpPort();
37 | exec("curl localhost:$httpPort/sql?mode=raw -d 'query=drop table if exists test' 2>&1");
38 | $query = 'INSERT into test(col1) VALUES(1) ';
39 | exec("curl localhost:$httpPort/sql?mode=raw -d 'query=$query' 2>&1", $out);
40 | $result = '[{"total":1,"error":"","warning":""}]';
41 | $this->assertEquals($result, $out[3]);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/test/Buddy/functional/ProcessErrorTest.php:
--------------------------------------------------------------------------------
1 | assertQueryResultContainsError(
21 | 'tratatata',
22 | "P02: syntax error, unexpected identifier near 'tratatata'"
23 | );
24 | $this->assertQueryResultContainsError(
25 | 'hello how are you?',
26 | "P02: syntax error, unexpected identifier near 'hello how are you?'"
27 | );
28 | $this->assertQueryResultContainsError(
29 | 'showf tables',
30 | "P02: syntax error, unexpected identifier near 'showf tables'"
31 | );
32 | }
33 |
34 | public function testCorrectErrorOnBackupNoTables(): void {
35 | $this->assertQueryResultContainsError(
36 | 'backup to /tmp',
37 | 'You have no tables to backup.'
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/Buddy/functional/ShowOpenTablesTest.php:
--------------------------------------------------------------------------------
1 | assertQueryResult(
40 | 'SHOW OPEN TABLES', [
41 | 'Table: a',
42 | 'Table: b',
43 | 'Table: test123',
44 | 'Table: hello',
45 | ]
46 | );
47 | }
48 |
49 | public function testShowOpenTablesFiltersLikeInAProperWay(): void {
50 | $this->assertQueryResult(
51 | "SHOW OPEN TABLES LIKE 'a'", [
52 | 'Table: a',
53 | ], [
54 | 'Table: b',
55 | 'Table: test123',
56 | 'Table: hello',
57 | ]
58 | );
59 |
60 | $this->assertQueryResult(
61 | "SHOW OPEN TABLES LIKE 'doesnotexist'", [
62 |
63 | ], [
64 | 'Table: a',
65 | 'Table: b',
66 | 'Table: test123',
67 | 'Table: hello',
68 | ]
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/test/Buddy/functional/ShowQueriesTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($expectedFields, $realFields);
32 | $query = ' show queries ';
33 | $out = static::runSqlQuery($query);
34 |
35 | $realFields = array_values(array_filter(array_map('trim', explode('|', $out[1]))));
36 | $this->assertEquals($expectedFields, $realFields);
37 | }
38 |
39 | public function testSQLShowQueriesFail(): void {
40 | echo "\nTesting the fail on the execution of SQL SHOW QUERIES statement\n";
41 | $query = 'SHOW QUERIES 123';
42 | $out = static::runSqlQuery($query);
43 | $result = [
44 | 'ERROR 1064 (42000) at line 1: P01: syntax error, unexpected identifier, '
45 | . "expecting VARIABLES near 'QUERIES 123'",
46 | ];
47 | $this->assertEquals($result, $out);
48 | }
49 |
50 | public function testHTTPShowQueriesOk(): void {
51 | echo "\nTesting the execution of HTTP SHOW QUERIES statement\n";
52 | $query = 'SHOW QUERIES';
53 | $out = static::runHttpQuery($query);
54 | $resultColumns = [
55 | ['id' => ['type' => 'long long']],
56 | ['query' => ['type' => 'string']],
57 | ['time' => ['type' => 'string']],
58 | ['protocol' => ['type' => 'string']],
59 | ['host' => ['type' => 'string']],
60 | ];
61 | if (!(isset($out[0]['columns'], $out[0]['total']))) {
62 | $this->fail('Unexpected response from searchd');
63 | }
64 | $this->assertEquals('', $out[0]['error']);
65 | $this->assertGreaterThan(0, $out[0]['total']);
66 | $this->assertEquals($resultColumns, $out[0]['columns']);
67 | }
68 |
69 | public function testHTTPShowQueriesFail(): void {
70 | echo "\nTesting the fail on the execution of HTTP SHOW QUERIES statement\n";
71 | $query = 'SHOW QUERIES 123';
72 | $out = static::runHttpQuery($query);
73 | $result = [
74 | 'error' => "P01: syntax error, unexpected identifier, expecting VARIABLES near 'QUERIES 123'",
75 | ];
76 | $this->assertEquals($result, $out);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/test/Buddy/functional/ShowVariablesTest.php:
--------------------------------------------------------------------------------
1 | assertQueryResult($query, ['Variable_name', 'Value', ...static::FIELDS]);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/Buddy/functional/config/manticore-bench.conf:
--------------------------------------------------------------------------------
1 | common {
2 | plugin_dir = /usr/local/lib/manticore
3 | lemmatizer_base = /usr/share/manticore/morph/
4 | }
5 | searchd {
6 | listen = 0.0.0.0:8312
7 | listen = 0.0.0.0:8306:mysql
8 | listen = 0.0.0.0:8308:http
9 | log = /var/log/manticore-test/searchd.log
10 | query_log = /var/log/manticore-test/query.log
11 | pid_file = /var/run/manticore-test/searchd.pid
12 | data_dir = /var/lib/manticore-test
13 | query_log_format = sphinxql
14 | buddy_path = manticore-executor /workdir/src/main.php
15 | threads = 2
16 | }
17 |
--------------------------------------------------------------------------------
/test/Buddy/functional/config/manticore.conf:
--------------------------------------------------------------------------------
1 | common {
2 | plugin_dir = /usr/local/lib/manticore
3 | lemmatizer_base = /usr/share/manticore/morph/
4 | }
5 | searchd {
6 | listen = 0.0.0.0:8312
7 | listen = 0.0.0.0:8306:mysql
8 | listen = 0.0.0.0:8308:http
9 | log = /var/log/manticore-test/searchd.log
10 | query_log = /var/log/manticore-test/query.log
11 | pid_file = /var/run/manticore-test/searchd.pid
12 | data_dir = /var/lib/manticore-test
13 | query_log_format = sphinxql
14 | buddy_path = manticore-executor /workdir/src/main.php --telemetry-period=10
15 | threads = 4
16 | }
17 |
--------------------------------------------------------------------------------
/test/Kafka/import.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | bootstrap_servers='localhost:9092'
4 | kafka_topic='my-data'
5 | json_file_path='/tmp/dump.json'
6 |
7 |
8 | /opt/bitnami/kafka/bin/kafka-console-producer.sh \
9 | --broker-list "$bootstrap_servers" \
10 | --property parse.key=true \
11 | --property "key.separator=:" \
12 | --topic "$kafka_topic" < "$json_file_path"
13 |
--------------------------------------------------------------------------------
/test/Plugin/CliTable/CliTableHandlerTest.php:
--------------------------------------------------------------------------------
1 | '',
45 | 'payload' => 'SHOW QUERIES',
46 | 'version' => Buddy::PROTOCOL_VERSION,
47 | 'format' => RequestFormat::SQL,
48 | 'endpointBundle' => ManticoreEndpoint::Cli,
49 | 'path' => 'cli',
50 | ]
51 | );
52 |
53 | self::setBuddyVersion();
54 | $serverUrl = self::setUpMockManticoreServer(false);
55 | $manticoreClient = new HTTPClient($serverUrl);
56 | $manticoreClient->setForceSync(true);
57 | Payload::$type = 'queries';
58 | $payload = Payload::fromRequest($request);
59 | $handler = new Handler($payload);
60 | $refCls = new ReflectionClass($handler);
61 | $refCls->getProperty('manticoreClient')->setValue($handler, $manticoreClient);
62 | go(
63 | function () use ($handler, $respBody) {
64 | $task = $handler->run();
65 | $task->wait(true);
66 |
67 | $this->assertEquals(true, $task->isSucceed());
68 | $result = $task->getResult()->getTableFormatted(0);
69 | $this->assertIsString($result);
70 | $this->assertStringContainsString($respBody, $result);
71 | self::finishMockManticoreServer();
72 | }
73 | );
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/test/Plugin/Insert/Exception/ParserLoadErrorTest.php:
--------------------------------------------------------------------------------
1 | expectException(ParserLoadError::class);
20 | $this->expectExceptionMessage('Test error message');
21 | throw new ParserLoadError('Test error message');
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/test/Plugin/Insert/InsertQuery/InsertQueryPayloadTest.php:
--------------------------------------------------------------------------------
1 | Buddy::PROTOCOL_VERSION,
25 | 'error' => '',
26 | 'payload' => 'INSERT INTO test(int_col, string_col, float_col, @timestamp)'
27 | . ' VALUES(1, \'string\', 2.22, \'2000-01-01T12:00:00Z\')',
28 | 'format' => RequestFormat::SQL,
29 | 'endpointBundle' => ManticoreEndpoint::Sql,
30 | 'path' => 'sql?mode=raw',
31 | ]
32 | );
33 | $payload = Payload::fromRequest($request);
34 | $this->assertInstanceOf(Payload::class, $payload);
35 |
36 | echo "\nTesting the prepared quries after creating request are correct\n";
37 |
38 | $this->assertIsArray($payload->queries);
39 | $this->assertEquals(2, sizeof($payload->queries));
40 | $this->assertEquals(
41 | [
42 | 'CREATE TABLE IF NOT EXISTS `test` (`int_col` int,`string_col` text,`float_col` float,'
43 | . '`@timestamp` timestamp)',
44 | 'INSERT INTO test(int_col, string_col, float_col, @timestamp) VALUES(1, \'string\', 2.22,'
45 | . ' \'2000-01-01T12:00:00Z\')',
46 | ], $payload->queries
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/Plugin/Insert/QueryParser/ParserLoaderTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(SQLInsertParser::class, $parser);
26 | $this->assertInstanceOf(InsertQueryParserInterface::class, $parser);
27 | $parser = Loader::getInsertQueryParser('test', Endpoint::Cli);
28 | $this->assertInstanceOf(SQLInsertParser::class, $parser);
29 | echo "\nGetting JSONInsertParser instance\n";
30 | $parser = Loader::getInsertQueryParser('insert', Endpoint::Insert);
31 | $this->assertInstanceOf(JSONInsertParser::class, $parser);
32 | try {
33 | $this->assertInstanceOf(ElasticJSONInsertParser::class, $parser);
34 | $this->fail();
35 | } catch (Exception) {
36 | }
37 | $parser = Loader::getInsertQueryParser('test/_doc/', Endpoint::Bulk);
38 | $this->assertInstanceOf(ElasticJSONInsertParser::class, $parser);
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/test/Plugin/Sharding/NodeTest.php:
--------------------------------------------------------------------------------
1 | 'localhost:9312',
17 | 'dfsdfsdf' => null,
18 | 'hello:world' => null,
19 | 'domain.local:9312' => 'domain.local:9312',
20 | '127.0.0.1:9312' => '127.0.0.1:9312',
21 | '127.0.0.1:9306:mysql' => null,
22 | '127.0.0.1:9308:http' => '127.0.0.1:9308',
23 | '9333' => '127.0.0.1:9333',
24 | '9234:http' => '127.0.0.1:9234',
25 | ];
26 |
27 | /**
28 | * Validate we can parse valid lines from config to get Node ID
29 | * @return void
30 | */
31 | public function testNodeIdParsing(): void {
32 | echo "\nTesting the parsing of node id from line\n";
33 | $map = static::TEST_MAP;
34 | // Edge case when we should detect hostname
35 | $hostname = gethostname();
36 | $host = gethostbyname($hostname ?: '');
37 | $map['0.0.0.0:9552'] = "$host:9552";
38 | $map['0.0.0.0:9552:http'] = "$host:9552";
39 | foreach ($map as $line => $expected) {
40 | // PHP has a bug and converts string into ints when can
41 | $nodeId = Node::parseNodeId((string)$line);
42 | $this->assertEquals($expected, $nodeId);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/bootstrap.php:
--------------------------------------------------------------------------------
1 | get('manticoreClient');
42 | $manticoreClient->setServerUrl('127.0.0.1:8312');
43 | MetricThread::setContainer($container);
44 | // phpcs:enable
45 |
--------------------------------------------------------------------------------
/test/src/Lib/BuddyRequestError.php:
--------------------------------------------------------------------------------
1 | $args
22 | * @return mixed
23 | */
24 | public static function invokeMethod(mixed $classInstance, string $methodName, array $args = []): mixed {
25 | $class = new \ReflectionClass($classInstance);
26 | $method = $class->getMethod($methodName);
27 | $method->setAccessible(true);
28 | $ref = gettype($classInstance) === 'string' ? null : $classInstance;
29 | return $method->invokeArgs($ref, $args);
30 | }
31 |
32 | /**
33 | * @param class-string|object $classInstance
34 | * @param string $methodName
35 | * @param array $args
36 | * @return array{0:string,1:string}
37 | */
38 | public static function getExceptionInfo(mixed $classInstance, string $methodName, array $args = []): array {
39 | $exCls = $exMsg = '';
40 | try {
41 | self::invokeMethod($classInstance, $methodName, $args);
42 | } catch (GenericError $e) {
43 | $exCls = $e::class;
44 | $exMsg = $e->getMessage();
45 | }
46 |
47 | return [$exCls, $exMsg];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------