diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..8ab3e39f8e82327cd91150f98055a518f5d6a708 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,110 @@ +image: golang:1.16 + +stages: + - test + - build + - docker + - deploy + +lint: + stage: test + script: + - make lint + +unit_tests: + stage: test + script: + - make dep + - make test + - gocover-cobertura < coverage.txt > coverage.xml + artifacts: + reports: + cobertura: coverage.xml + +race: + stage: test + script: + - make race + +coverage: + stage: test + script: + - make coverage + after_script: + - mkdir $CI_COMMIT_BRANCH + - cp cover.html $CI_COMMIT_BRANCH + - mv $CI_COMMIT_BRANCH/cover.html $CI_COMMIT_BRANCH/index.html + + - if [[ $CI_COMMIT_BRANCH = "develop" || $CI_COMMIT_BRANCH = "main" ]]; then COVERAGE_PATH=""; else COVERAGE_PATH="features"; fi + + # install openssh client and add ssh keys + - apt-get install openssh-client curl -y >/dev/null + - mkdir ~/.ssh/ + - eval $(ssh-agent -s) + - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa + - chmod 600 ~/.ssh/id_rsa + - ssh-add ~/.ssh/id_rsa + - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts + - chmod 644 ~/.ssh/known_hosts + - ssh -fN -L 1234:science-vs260.science.uu.nl:22 sivan@up.science.uu.nl + - scp -r -o StrictHostKeyChecking=no -P 1234 -i ~/.ssh/id_rsa $CI_COMMIT_BRANCH root@localhost:/datadisk/documentation-coverage/home/backend/$CI_PROJECT_NAME/$COVERAGE_PATH + artifacts: + untracked: false + expire_in: 30 days + paths: + - cover.html + +build: + stage: build + script: + - make linux + only: + - develop + artifacts: + paths: + - builds/ + dependencies: [] + + +docker: + image: docker:stable + tags: + - docker + stage: docker + only: + - develop + before_script: + - docker login datastropheregistry.azurecr.io -u $REGISTRY_USERNAME -p $REGISTRY_PASSWORD + script: + - ls + - cd builds + - ls + - cd .. + - docker build --progress plain -t $CI_PROJECT_NAME:latest . + - docker tag $CI_PROJECT_NAME datastropheregistry.azurecr.io/$CI_PROJECT_NAME:latest + - docker push datastropheregistry.azurecr.io/$CI_PROJECT_NAME:latest + dependencies: + - build + + +deploy: + stage: deploy + only: + - develop + script: + - apt-get install openssh-client curl -y >/dev/null + - mkdir ~/.ssh/ + - eval $(ssh-agent -s) + - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa + - chmod 600 ~/.ssh/id_rsa + - ssh-add ~/.ssh/id_rsa + - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts + - chmod 644 ~/.ssh/known_hosts + - ssh -fN -L 1234:science-vs260.science.uu.nl:22 sivan@up.science.uu.nl + # Copy kubernetes files over + - scp -r -o StrictHostKeyChecking=no -P 1234 -i ~/.ssh/id_rsa deployments/* root@localhost:/root/kubernetes/$CI_PROJECT_NAME + # Deploy all yml files + - ssh -p 1234 -i ~/.ssh/id_rsa root@localhost "for i in kubernetes/$CI_PROJECT_NAME/*.yml; do kubectl apply -f \$i; done" + # Perform rolling update for deployment + - ssh -p 1234 -i ~/.ssh/id_rsa root@localhost "kubectl rollout restart -f kubernetes/$CI_PROJECT_NAME/deployment.yml" + dependencies: [] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..8d02ac60fcf1b48db639ff42dc3e2248cd4fda81 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM busybox +ADD ./builds/main / +CMD /main diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..c95e9e41833d9b60e9b9af58a5dfd2f0e8c38997 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +.PHONY: all dep build test lint + +lint: dep ## Lint the files + @golint -set_exit_status ./... + +test: ## Run unittests + @go test -cover -coverprofile=coverage.txt -covermode count ./... + +race: dep ## Run data race detector + @go test -race -short ./... + +dep: ## Get the dependencies + @go get -v -d ./... + @go get -u golang.org/x/lint/golint + @go get -u github.com/boumenot/gocover-cobertura + +coverage: dep + @go test -v -coverpkg=./... -coverprofile=cover.out ./... + @go tool cover -func cover.out | grep total + @go tool cover -html=cover.out -o cover.html + +windows: + $(eval export GOOS := windows) + @go build -o builds/main ./cmd/query-service/ + +macos: + $(eval export GOOS := darwin) + @go build -o builds/main ./cmd/query-service/ + +linux: # Build for linux + $(eval export GOOS := linux) + CGO_ENABLED=0 go build -o builds/main ./cmd/query-service/ + +run: + ./builds/main diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6ae9ff2924b491bb3c50cfa3610f78c46d5709f1 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Service Template + +Keep in mind that not all folders are required, only the `/builds`, `/cmd/app-name` and `/coverage` folders are required. \ No newline at end of file diff --git a/cmd/README.md b/cmd/README.md new file mode 100644 index 0000000000000000000000000000000000000000..413366820d44d9b71f95b3f680e25d6e98178335 --- /dev/null +++ b/cmd/README.md @@ -0,0 +1,9 @@ +# `/cmd` + +Main applications for this project. + +The directory name for each application should match the name of the executable you want to have (e.g., /cmd/myapp). + +Don't put a lot of code in the application directory. If you think the code can be imported and used in other projects, then it should live in the /pkg directory. If the code is not reusable or if you don't want others to reuse it, put that code in the /internal directory. You'll be surprised what others will do, so be explicit about your intentions! + +It's common to have a small main function that imports and invokes the code from the /internal and /pkg directories and nothing else. \ No newline at end of file diff --git a/cmd/query-service/main.go b/cmd/query-service/main.go new file mode 100644 index 0000000000000000000000000000000000000000..370e4e114914282b5a598e76833661721c26eee3 --- /dev/null +++ b/cmd/query-service/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "query-service/internal/aql" + "query-service/internal/consumer" + "query-service/internal/errorhandler" +) + +func main() { + + exchangeID := "query-requests" + routingKey := "aql-user-request" + consumer.StartConsuming(onMessageReceived, exchangeID, routingKey) +} + +func onMessageReceived(jsonMsg *[]byte) { + // Retrieve JSON formatted string payload from msg + + // Convert the json byte msg to an aql query string + aqlQuery, err := aql.ConvertJSONToAQL(jsonMsg) + errorhandler.FailWithError(err, "failed to parse incoming msg to AQL") // TODO: don't panic on error, send error message to client instead + + fmt.Println(*aqlQuery) + + // execute and retrieve result + + // convert result to general (node-link (?)) format + + // publish converted result +} diff --git a/deployments/README.md b/deployments/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f6ae8c3eeecb33fb7525ba6f49430584511551bb --- /dev/null +++ b/deployments/README.md @@ -0,0 +1,3 @@ +# `/deployments` + +Docker and Kubernetes yaml files. \ No newline at end of file diff --git a/deployments/deployment.yml b/deployments/deployment.yml new file mode 100644 index 0000000000000000000000000000000000000000..d18bb006265e352e7e3d11bd47ae803eaab76968 --- /dev/null +++ b/deployments/deployment.yml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: query-handler-deployment + labels: + app: query-handler +spec: + replicas: 3 + selector: + matchLabels: + app: query-handler + template: + metadata: + labels: + app: query-handler + spec: + containers: + - name: query-handler + image: datastropheregistry.azurecr.io/query-handler-service:latest + ports: + - containerPort: 3000 + env: + - name: RABBIT_HOST + value: rabbitmq + - name: RABBIT_PORT + value: "5672" + - name: RABBIT_USER + value: I9YPuqNYvN_o4597-LJ6i0sWZTDTV5kk + - name: RABBIT_PASSWORD + value: zBA4m-IzK6ejLtCdr2gxB6kHmURaUvy4 + imagePullSecrets: + - name: docker-regcred \ No newline at end of file diff --git a/deployments/svc.yml b/deployments/svc.yml new file mode 100644 index 0000000000000000000000000000000000000000..4280c417900298b08e6e276ddb96617c335ccebf --- /dev/null +++ b/deployments/svc.yml @@ -0,0 +1,11 @@ +# Service that exposes this deployment +kind: Service +apiVersion: v1 +metadata: + name: query-handler-service +spec: + selector: + app: query-handler + ports: + - port: 3000 + targetPort: 3000 \ No newline at end of file diff --git a/go.mod b/go.mod index ddb2856fe29be242ee34654afe1d1cdeace543ef..99a30d49101c9b0b2d6ee79779fad316cd013bc4 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,8 @@ module query-service go 1.15 -require github.com/arangodb/go-driver v0.0.0-20210304082257-d7e0ea043b7f // indirect +require ( + github.com/streadway/amqp v1.0.0 + github.com/stretchr/testify v1.7.0 // indirect + github.com/thijsheijden/alice v0.1.5 +) diff --git a/go.sum b/go.sum index 07ff4346a4886d4e7b511095e4c94ebe6c11c627..385ffb66da0c48ce4f8170578fc56dc04f336b06 100644 --- a/go.sum +++ b/go.sum @@ -1,42 +1,14 @@ -github.com/arangodb/go-driver v0.0.0-20210304082257-d7e0ea043b7f h1:MEdxM6EhSFo2ecumBN0CC6s1zMWDpNvcmDIHEfMvl18= -github.com/arangodb/go-driver v0.0.0-20210304082257-d7e0ea043b7f/go.mod h1:3NUekcRLpgheFIGEwcOvxilEW73MV1queNKW58k7sdc= -github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e h1:Xg+hGrY2LcQBbxd0ZFdbGSyRKTYMZCfBbw/pMJFOk1g= -github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e/go.mod h1:mq7Shfa/CaixoDxiyAAc5jZ6CVBAyPaNQCGS7mkj4Ho= -github.com/coreos/go-iptables v0.4.3/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/google/addlicense v0.0.0-20200817051935-6f4cd4aacc89/go.mod h1:EMjYTRimagHs1FwlIqKyX3wAM0u3rA+McvlIIWmSamA= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= +github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= +github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/thijsheijden/alice v0.1.5 h1:kOZHhLGSHMja77I/Wvd19M7nvxgEWVIJ4vk5antCKdQ= +github.com/thijsheijden/alice v0.1.5/go.mod h1:UypS/UTucbp+fmG1JtmXGCKPnGKimAC9AgmJ8iGoLNo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/README.md b/internal/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0f6fea0aa424dbf5816546a885eed082e78ff71f --- /dev/null +++ b/internal/README.md @@ -0,0 +1,3 @@ +# `/internal` + +Private application and library code. This is the code you don't want others importing in their applications or libraries. Note that this layout pattern is enforced by the Go compiler itself. See the Go 1.4 [`release notes`](https://golang.org/doc/go1.4#internalpackages) for more details. Note that you are not limited to the top level `internal` directory. You can have more than one `internal` directory at any level of your project tree. diff --git a/internal/aql/aql.go b/internal/aql/aql.go new file mode 100644 index 0000000000000000000000000000000000000000..3d9c0f8da4f0f1c713b19655ff69037374631ac0 --- /dev/null +++ b/internal/aql/aql.go @@ -0,0 +1,293 @@ +package aql + +import ( + "encoding/json" + "fmt" + "strconv" +) + +// Constraint datatypes +// text MatchTypes: exact/contains/startswith/endswith +// number MatchTypes: GT/LT/EQ +// bool MatchTypes: EQ/NEQ + +// Ranges dus tussen 10 half 5 bijv. + +// Struct used for JSON conversion +type parsedJSON struct { + Return returnStruct + Nodes []nodeStruct + Relations []relationStruct +} + +type returnStruct struct { + Entities []int + Relations []int +} + +type nodeStruct struct { + NodeType string + Constraints map[string]constraintStruct +} +type relationStruct struct { + RelationType string + NodeFrom int + NodeTo int + Depth searchDepthStruct + Constraints map[string]constraintStruct +} +type searchDepthStruct struct { + Min int + Max int +} + +type constraintStruct struct { + Value string + DataType string + MatchType string +} + +// ConvertJSONToAQL converts a json string to an AQL query +func ConvertJSONToAQL(jsonMsg *[]byte) (*string, error) { + + jsonStruct, err := convertJSONToStruct(jsonMsg) + if err != nil { + fmt.Println(err) + return nil, err + } + + //Per node query + // per constraint + //relations koppelen ze samen + //return statement + + result := createAllNodesQuery(jsonStruct.Return.Entities, jsonStruct.Nodes) + return result, nil +} + +func createAllNodesQuery(returnEntitiesIndices []int, nodes []nodeStruct) *string { + var result string + for nodeIndex := range returnEntitiesIndices { + nodeID := fmt.Sprintf("n%s", strconv.Itoa(nodeIndex)) + + nodeQueryString := *createNodeQuery(&nodes[nodeIndex], nodeID) + + result += fmt.Sprintf(" \n%s", nodeQueryString) + } + + return &result +} + +// createNodeQuery converts the node part of the json to a subquery +func createNodeQuery(node *nodeStruct, name string) *string { + /* + LET alices = ( + FOR x IN female + FILTER x.name == "Alice" AND x.birth_year > 1997 + RETURN x + ) + + NAAR --> + + LET {NAAM**} = ( + FOR x IN {NODETYPE} + FILTER x.{CONSTRAINT[0]} {{CONSTRAINT[0]}.MATCHTYPE} {CONSTRAINT[0].VALUE} + AND x.{CONSTRAINT[1]} {{CONSTRAINT[1]}.MATCHTYPE} {CONSTRAINT[1].VALUE} + RETURN x + ) + + */ + + letStatement := fmt.Sprintf("LET %s = (\n", name) + forStatement := fmt.Sprintf("\tFOR x IN %s \n", node.NodeType) + + // Generate all constraints as FILTER statements + first := true + filter := "\tFILTER " + for key, constraint := range node.Constraints { + constraint := createQueryConstraint(&constraint, key) + + if first { + filter += fmt.Sprintf("\t%s ", *constraint) + first = false + } else { + filter += fmt.Sprintf("AND\n\t\t%s", *constraint) + } + } + + returnStatement := "\n\tRETURN x\n)" + + // Concatenate all the statements + result := letStatement + forStatement + filter + returnStatement + return &result +} + +// Constraint datatypes +// text MatchTypes: exact/contains/startswith/endswith +// number MatchTypes: GT/LT/EQ +// bool MatchTypes: EQ/NEQ + +// createQueryConstraint creates a sinlge line of AQL filtering/constraint +func createQueryConstraint(con *constraintStruct, key string) *string { + //FILTER x.{CONSTRAINT[0]} {{CONSTRAINT[0]}.MATCHTYPE} {CONSTRAINT[0].VALUE} + // name mtch val + dataType + var ( + mtch string + val string + line string + ) + + //Wicked switches letsgo + switch con.DataType { + case "text": + val = fmt.Sprintf("\"%s\"", con.Value) + switch con.MatchType { + case "contains": + mtch = "IN" + case "startswith": + mtch = "LIKE" + val = fmt.Sprintf("\"%s%%\"", con.Value) + case "endswith": + mtch = "LIKE" + val = fmt.Sprintf("\"_%s\"", con.Value) + default: //exact + mtch = "==" + } + case "number": + val = con.Value + switch con.MatchType { + case "GT": + mtch = ">" + case "LT": + mtch = "<" + case "GET": + mtch = ">=" + case "LET": + mtch = "<=" + default: //EQ + mtch = "==" + } + default: /*bool*/ + val = con.Value + switch con.MatchType { + case "NEQ": + mtch = "!=" + default: //EQ + mtch = "==" + } + } + line = fmt.Sprintf("x.%s %s %s", key, mtch, val) + return &line +} + +func convertJSONToStruct(jsonMsg *[]byte) (*parsedJSON, error) { + jsonStruct := parsedJSON{} + err := json.Unmarshal(*jsonMsg, &jsonStruct) + + if err != nil { + return nil, err + } + + return &jsonStruct, nil +} + +/* + desired output + + WITH male + FOR a1 IN female + FILTER a1.name == "Alice" + + (FOR r1v,r1e,r1p IN 1..1 OUTBOUND a1 relation + FILTER r1v.name == "Bob") + OR + (FOR r2v,r2e,r2p IN 1..1 OUTBOUND a1 relation + FILTER r2v.name == "Martin" && r2v.hasdog == true) + // constraints for Bob + OR r1v.name == "Martin" + FILTER r1e.type == "married" + + FOR r2v,r2e,r2p IN 1..1 OUTBOUND r1v relation + FILTER r2v.name == "Figo" + FILTER r2e.type == "has_dog" + + RETURN {a1,r1v,r2v} + + + of + + + LET alices = ( + FOR x IN female + FILTER x.name == "Alice" + RETURN x + ) + + LET bobs = ( + FOR x IN male + FILTER x.name == "Bob" + RETURN x + ) + + LET alices = ( + FOR alice IN alices + FOR r1v,r1e,r1p IN 1..1 OUTBOUND alice relation + FILTER r1v IN bobs AND r1e.type == "married" + RETURN {alice} + ) + + LET alices = ( + FOR alice IN alices + FOR r1v,r1e,r1p IN 1..1 OUTBOUND alice relation + FILTER r1v IN marting AND r1e.type == "friend" + RETURN {alice} + ) + + FOR a2 IN male + FILTER a2.name == "Bob" + + FOR r1v,r1e,r1p IN 1..1 OUTBOUND a1 relation + FILTER r1v._id == a2._id + FILTER r1e.type == "married" + RETURN {a1,a2,r1e} + +*/ +/* +{ + "NodeType": "female", + "Constraints": + { + "name": { "Value": "Alice", "DataType": "text", "MatchType": "exact" }, + "birth_year": { "Value": "1997", "DataType": "number", "MatchType": "GT" } + } + } + + NAAR --> + + LET alices = ( + FOR x IN female + FILTER x.name == "Alice" AND x.birth_year > 1997 + RETURN x + ) + +*/ + +//Nog een manier vinden om namen te syncen over de queries heen ** + +// func forGlory() *constraintStruct { +// var yeet constraintStruct +// yeet = constraintStruct{ +// ConstraintName: "name", +// Value: "Alice", +// MatchType: "exact", +// DataType: "text", +// } +// return &yeet +// } + +// func alsoForGlory() { +// con := forGlory() + +// toPrint := createQueryConstraint(*con) +// fmt.Println(*toPrint) +// } diff --git a/internal/aql/aql_test.go b/internal/aql/aql_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3c657c580f1e5fbb26e25c5d479dcfa85650609f --- /dev/null +++ b/internal/aql/aql_test.go @@ -0,0 +1,12 @@ +package aql + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMock(t *testing.T) { + + assert.True(t, true, true) +} diff --git a/internal/consumer/consumer.go b/internal/consumer/consumer.go new file mode 100644 index 0000000000000000000000000000000000000000..f0a6a630402e46470549bbca2d10f612ac2a5391 --- /dev/null +++ b/internal/consumer/consumer.go @@ -0,0 +1,48 @@ +package consumer + +import ( + "os" + "query-service/internal/errorhandler" + "strconv" + "time" + + "github.com/streadway/amqp" + "github.com/thijsheijden/alice" +) + +// ConsumeMessageFunc is a function type to be called when a message is consumed +type ConsumeMessageFunc func(*[]byte) + +// StartConsuming will start consuming messages +// When a message is received the consumeMessage function will be called +func StartConsuming(consumeMessage ConsumeMessageFunc, exchangeID string, routingKey string) { + + // Create connection config using environment variables + rabbitUser := os.Getenv("RABBIT_USER") + rabbitPassword := os.Getenv("RABBIT_PASSWORD") + rabbitHost := os.Getenv("RABBIT_HOST") + rabbitPort, err := strconv.Atoi(os.Getenv("RABBIT_PORT")) + + config := alice.CreateConfig(rabbitUser, rabbitPassword, rabbitHost, rabbitPort, true, time.Minute*1, alice.DefaultErrorHandler) + + // Open connection to broker + conn := alice.Connect(*config) + + // Declare the exchange we want to bind to + exchange, err := alice.CreateDefaultExchange(exchangeID, alice.Direct) + if err != nil { + errorhandler.FailWithError(err, "failed to create exchange") + } + + // Declare the queue we will consume from + queue := alice.CreateQueue(exchange, "", true, false, true, false, nil) + + // Create the consumer + c, err := conn.CreateConsumer(queue, routingKey, alice.DefaultConsumerErrorHandler) + if err != nil { + errorhandler.FailWithError(err, "failed to create consumer") + } + + // Start consuming messages + c.ConsumeMessages(nil, false, func(msg amqp.Delivery) { consumeMessage(&msg.Body) }) +} diff --git a/internal/errorhandler/errorhandler.go b/internal/errorhandler/errorhandler.go new file mode 100644 index 0000000000000000000000000000000000000000..98047a959e69fdc6e191e2aab660a858be6bf8d3 --- /dev/null +++ b/internal/errorhandler/errorhandler.go @@ -0,0 +1,20 @@ +package errorhandler + +import ( + "fmt" +) + +// LogError logs an error that is not nil +func LogError(err error, msg string) { + if err != nil { + fmt.Printf("%s: %v", msg, err) + } +} + +// FailWithError panics if the error is not nil +func FailWithError(err error, msg string) { + if err != nil { + fmt.Printf("%s: %v", msg, err) + panic(err) + } +} diff --git a/pkg/README.md b/pkg/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b40edadf3b2eb4dd81e36d8cc026cbc0b0afeeaa --- /dev/null +++ b/pkg/README.md @@ -0,0 +1,3 @@ +# `/pkg` + +Library code that's ok to use by external applications (e.g., /pkg/mypubliclib). Other projects will import these libraries expecting them to work, so think twice before you put something here :-) Note that the internal directory is a better way to ensure your private packages are not importable because it's enforced by Go. The /pkg directory is still a good way to explicitly communicate that the code in that directory is safe for use by others. The I'll take pkg over internal blog post by Travis Jeffery provides a good overview of the pkg and internal directories and when it might make sense to use them. \ No newline at end of file