diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 91ef26d2e..28470c3e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,6 @@ name: Go +permissions: + contents: read # # git hub actions config file. https://docs.github.com/en/actions @@ -13,38 +15,58 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: - go-version: ^1.18 + go-version: ^1.23.6 - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Build run: go build -v ./ - name: Store grip - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: gripBin path: grip + unitTests: needs: build name: Unit tests runs-on: ubuntu-latest steps: - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: - go-version: ^1.18 + go-version: ^1.23.6 - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: run unit tests run: | - go test ./test/... -config badger.yml - - + go test ./test/... -config pebble.yml + + kafkaTest: + needs: build + name: Kafka tests + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Python Dependencies for Conformance + run: pip install requests numpy kafka-python + - name: Download grip + uses: actions/download-artifact@v4 + with: + name: gripBin + - name: Kafka Test + run: | + chmod +x grip + make start-kafka + ./grip server -c ./test/kafka.yml & + sleep 5 + python conformance/run_kafka.py badgerTest: needs: build @@ -52,11 +74,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Python Dependencies for Conformance run: pip install requests numpy - name: Download grip - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: gripBin - name: Badger Test @@ -65,7 +87,6 @@ jobs: ./grip server --rpc-port 18202 --http-port 18201 --config ./test/badger.yml & sleep 5 make test-conformance - pebbleTest: needs: build @@ -73,11 +94,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Python Dependencies for Conformance run: pip install requests numpy - name: Download grip - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: gripBin - name: Pebble Test @@ -86,7 +107,6 @@ jobs: ./grip server --rpc-port 18202 --http-port 18201 --config ./test/pebble.yml & sleep 5 make test-conformance - mongoTest: needs: build @@ -94,11 +114,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Python Dependencies for Conformance run: pip install requests numpy - name: Download grip - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: gripBin - name: Mongo Conformance @@ -107,8 +127,7 @@ jobs: make start-mongo ./grip server --rpc-port 18202 --http-port 18201 --config ./test/mongo.yml & sleep 5 - make test-conformance - + python conformance/run_conformance.py http://localhost:18201 --exclude nested_index mongoCoreTest: needs: build @@ -116,67 +135,62 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Python Dependencies for Conformance run: pip install requests numpy - name: Download grip - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: gripBin - - name: Mongo Conformance + - name: Mongo Core Conformance run: | chmod +x grip make start-mongo ./grip server --rpc-port 18202 --http-port 18201 --config ./test/mongo-core-processor.yml & sleep 5 make test-conformance - - elasticTest: + postgresTest: needs: build - name: Elastic Test + name: Postgres Test runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Python Dependencies for Conformance run: pip install requests numpy - name: Download grip - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: gripBin - - name: Elastic Conformance + - name: Postgres Conformance run: | chmod +x grip - make start-elastic + make start-postgres sleep 15 - ./grip server --rpc-port 18202 --http-port 18201 --config ./test/elastic.yml & + ./grip server --rpc-port 18202 --http-port 18201 --config ./test/psql.yml & sleep 5 - make test-conformance - + python conformance/run_conformance.py http://localhost:18201 --exclude aggregations - portgresTest: + sqliteTest: needs: build - name: Portgres Test + name: Sqlite Test runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Python Dependencies for Conformance run: pip install requests numpy - name: Download grip - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: gripBin - - name: Postgres Conformance + - name: Sqlite Conformance run: | chmod +x grip - make start-postgres - sleep 15 - ./grip server --rpc-port 18202 --http-port 18201 --config ./test/psql.yml & + ./grip server --rpc-port 18202 --http-port 18201 --config ./test/sqlite.yml & sleep 5 - python conformance/run_conformance.py http://localhost:18201 --exclude index aggregations - + python conformance/run_conformance.py http://localhost:18201 --exclude aggregations gripperTest: needs: build @@ -184,13 +198,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Update pip run: pip install --upgrade pip - name: Python Dependencies for Conformance run: pip install -U requests numpy grpcio-tools protobuf - name: Download grip - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: gripBin - name: Gripper Conformance @@ -201,8 +215,7 @@ jobs: sleep 5 ./grip server --rpc-port 18202 --http-port 18201 --config ./gripper/test-graph/config.yaml --er tableServer=localhost:50051 & sleep 5 - python conformance/run_conformance.py http://localhost:18201 --readOnly swapi - + python conformance/run_conformance.py http://localhost:18201 --readOnly swapi --exclude index pivot authTest: needs: build @@ -210,52 +223,63 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Python Dependencies for Conformance run: pip install requests numpy PyYAML - name: Download grip - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: gripBin - name: Auth Test run: | # start grip server chmod +x grip - ./grip server --rpc-port 18202 --http-port 18201 --config ./test/badger-auth.yml & + ./grip server --rpc-port 18202 --http-port 18201 --config ./test/pebble-auth.yml & sleep 5 # simple auth - # run tests without credentials, should fail + # run tests without credentials, should fail if make test-conformance then echo "ERROR: Conformance tests ran without credentials." ; exit 1 else - echo "Got expected auth error" - fi + echo "Got expected auth error" + fi # run specialized role based tests - make test-authorization ARGS="--grip_config_file_path test/badger-auth.yml" - - - + make test-authorization ARGS="--grip_config_file_path test/pebble-auth.yml" - #gridsTest: - # needs: build - # name: GRIDs Conformance - # runs-on: ubuntu-latest - # steps: - # - name: Check out code - # uses: actions/checkout@v2 - # - name: Python Dependencies for Conformance - # run: pip install requests numpy - # - name: Download grip - # uses: actions/download-artifact@v2 - # with: - # name: gripBin - # - name: GRIDs unit tests - # run: | - # chmod +x grip - # go test ./test -config grids.yml - # - name: GRIDs Test - # run: | - # ./grip server --rpc-port 18202 --http-port 18201 --config ./test/grids.yml & - # sleep 5 - # make test-conformance + gridsTest: + needs: build + name: GRIDs Conformance + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Python Dependencies for Conformance + run: pip install requests numpy + - name: Download grip + uses: actions/download-artifact@v4 + with: + name: gripBin + - name: GRIDs Test + run: | + chmod +x grip + ./grip server --rpc-port 18202 --http-port 18201 --config ./test/grids.yml & + sleep 5 + make test-conformance + + + gridsUnitTest: + needs: build + name: GRIDs Unit Test + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Download grip + uses: actions/download-artifact@v4 + with: + name: gripBin + - name: GRIDs unit tests + run: | + chmod +x grip + go test ./test -config grids.yml diff --git a/.gitignore b/.gitignore index a7c43db8e..75eb8e5cf 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ gripql/R/*.tar.gz # dev tools .idea/ .vscode/ +.DS_Store \ No newline at end of file diff --git a/Makefile b/Makefile index 8c3ff1b3a..4fbae6417 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ VERSION_LDFLAGS=\ -X "github.com/bmeg/grip/version.GitBranch=$(git_branch)" \ -X "github.com/bmeg/grip/version.GitUpstream=$(git_upstream)" -export GRIP_VERSION = 0.7.0 +export GRIP_VERSION = 0.8.0 # LAST_PR_NUMBER is used by the release notes builder to generate notes # based on pull requests (PR) up until the last release. export LAST_PR_NUMBER = 229 @@ -41,12 +41,10 @@ proto: --go_opt paths=source_relative \ --go-grpc_out ./ \ --go-grpc_opt paths=source_relative \ - --grpc-gateway_out ./ \ + --grpc-gateway_out allow_delete_body=true:./ \ --grpc-gateway_opt logtostderr=true \ --grpc-gateway_opt paths=source_relative \ --grpc-rest-direct_out . \ - --grpc-gateway-client_out . \ - --grpc-gateway-client_opt paths=source_relative \ gripql.proto @cd kvindex && protoc \ -I ./ \ @@ -66,11 +64,11 @@ proto: proto-depends: @git submodule update --init --recursive - @go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.11.1 - @go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@v2.11.1 - @go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 + @go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest + @go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest + @go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.2 @go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest - @go install github.com/ckaznocha/protoc-gen-lint@v0.2.4 + @go install github.com/ckaznocha/protoc-gen-lint@latest @go install github.com/bmeg/protoc-gen-grpc-rest-direct@latest @go install github.com/ckaznocha/protoc-gen-lint@latest @@ -91,7 +89,7 @@ lint: flake8 gripql/python/ conformance/ lint-depends: - go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.35.2 + go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.1 go install golang.org/x/tools/cmd/goimports # --------------------- @@ -129,11 +127,7 @@ test-authorization: # --------------------- start-mongo: @docker rm -f grip-mongodb-test > /dev/null 2>&1 || echo - docker run -d --name grip-mongodb-test -p 27017:27017 docker.io/mongo:3.6.4 > /dev/null - -start-elastic: - @docker rm -f grip-es-test > /dev/null 2>&1 || echo - docker run -d --name grip-es-test -p 19200:9200 -p 9300:9300 -e "discovery.type=single-node" -e "xpack.security.enabled=false" docker.elastic.co/elasticsearch/elasticsearch:5.6.3 > /dev/null + docker run -d --name grip-mongodb-test -p 27017:27017 mongo:7.0.13-rc0-jammy > /dev/null start-postgres: @docker rm -f grip-postgres-test > /dev/null 2>&1 || echo @@ -146,6 +140,52 @@ start-mysql: start-gripper-test: @cd ./gripper/test-graph && ./gripper-table -m swapi/table.map & +start-kafka: + @docker rm -f kafka > /dev/null 2>&1 || echo + docker run -d --name kafka \ + -p 9092:9092 \ + -e KAFKA_ENABLE_KRAFT=yes \ + -e KAFKA_KRAFT_CLUSTER_ID=abcdefghijklmnopqrstuv== \ + -e KAFKA_CFG_NODE_ID=1 \ + -e KAFKA_CFG_PROCESS_ROLES=controller,broker \ + -e KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \ + -e KAFKA_CFG_LISTENERS=CONTROLLER://:9093,INTERNAL://:9092 \ + -e KAFKA_CFG_ADVERTISED_LISTENERS=INTERNAL://localhost:9092 \ + -e KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=INTERNAL:SASL_PLAINTEXT,CONTROLLER:PLAINTEXT \ + -e KAFKA_CFG_INTER_BROKER_LISTENER_NAME=INTERNAL \ + -e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER \ + -e KAFKA_CFG_SUPER_USERS=User:admin \ + -e KAFKA_CLIENT_USERS=admin \ + -e KAFKA_CLIENT_PASSWORDS=adminpassword \ + -e KAFKA_CFG_SASL_ENABLED_MECHANISMS=PLAIN \ + -e KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL=PLAIN \ + bitnami/kafka:latest + printf '%s\n' \ + 'security.protocol=SASL_PLAINTEXT' \ + 'sasl.mechanism=PLAIN' \ + 'sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="adminpassword";' \ + > sasl-config.properties + docker cp sasl-config.properties kafka:/tmp/sasl-config.properties + @echo "Waiting for Kafka to become ready..." + @until docker exec kafka kafka-topics.sh \ + --list \ + --bootstrap-server localhost:9092 \ + --command-config /tmp/sasl-config.properties > /dev/null 2>&1; do \ + echo "Still waiting..."; \ + sleep 2; \ + done + docker exec kafka kafka-topics.sh \ + --create \ + --topic gripHistory \ + --bootstrap-server localhost:9092 \ + --partitions 1 \ + --replication-factor 1 \ + --command-config /tmp/sasl-config.properties + docker exec kafka kafka-topics.sh \ + --list \ + --bootstrap-server localhost:9092 \ + --command-config /tmp/sasl-config.properties + # --------------------- # Website # --------------------- @@ -160,3 +200,4 @@ website-dev: # Other # --------------------- .PHONY: test rocksdb website + diff --git a/README.md b/README.md index 3bc844768..faa2232e3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ https://bmeg.github.io/grip/ -GRIP stands for GRaph Integration Platform. It provides a graph interface on top of a variety of existing database technologies including: MongoDB, Elasticsearch, PostgreSQL, MySQL, MariaDB, Badger, and LevelDB. +GRIP stands for GRaph Integration Platform. It provides a graph interface on top of a variety of existing database technologies including: MongoDB, PostgreSQL, MySQL, MariaDB, Badger, and LevelDB. Properties of an GRIP graph: diff --git a/accounts/basic.go b/accounts/basic.go index ffbf6b119..780ae28a2 100644 --- a/accounts/basic.go +++ b/accounts/basic.go @@ -4,6 +4,8 @@ import ( "encoding/base64" "fmt" "strings" + + "github.com/bmeg/grip/log" ) // BasicCredential describes a username and password for use with Funnel's basic auth. @@ -18,7 +20,7 @@ func (ba BasicAuth) Validate(md MetaData) (string, error) { var auth []string var ok bool - fmt.Printf("Running BasicAuth: %#v\n", md) + log.Infof("Running BasicAuth: %#v\n", md) if auth, ok = md["Authorization"]; !ok { if auth, ok = md["authorization"]; !ok { @@ -28,7 +30,7 @@ func (ba BasicAuth) Validate(md MetaData) (string, error) { if len(auth) > 0 { user, password, ok := parseBasicAuth(auth[0]) - fmt.Printf("User: %s Password: %s OK: %s\n", user, password, ok) + log.Infof("Authenticating as User: %s OK: %t\n", user, ok) for _, c := range ba { if c.User == user && c.Password == password { return user, nil diff --git a/accounts/bulk_write_filter.go b/accounts/bulk_write_filter.go index dafa875cb..01e79bac2 100644 --- a/accounts/bulk_write_filter.go +++ b/accounts/bulk_write_filter.go @@ -31,11 +31,11 @@ func (bw *BulkWriteFilter) Context() context.Context { return bw.SS.Context() } -func (bw *BulkWriteFilter) SendMsg(m interface{}) error { +func (bw *BulkWriteFilter) SendMsg(m any) error { return bw.SS.SendMsg(m) } -func (bw *BulkWriteFilter) RecvMsg(m interface{}) error { +func (bw *BulkWriteFilter) RecvMsg(m any) error { for { var ge gripql.GraphElement err := bw.SS.RecvMsg(&ge) @@ -52,3 +52,47 @@ func (bw *BulkWriteFilter) RecvMsg(m interface{}) error { } } } + +type BulkWriteRawFilter struct { + SS grpc.ServerStream + User string + Access Access +} + +func (bw *BulkWriteRawFilter) SetHeader(m metadata.MD) error { + return bw.SS.SendHeader(m) +} + +func (bw *BulkWriteRawFilter) SendHeader(m metadata.MD) error { + return bw.SS.SendHeader(m) +} + +func (bw *BulkWriteRawFilter) SetTrailer(m metadata.MD) { + bw.SS.SetTrailer(m) +} + +func (bw *BulkWriteRawFilter) Context() context.Context { + return bw.SS.Context() +} + +func (bw *BulkWriteRawFilter) SendMsg(m any) error { + return bw.SS.SendMsg(m) +} + +func (bw *BulkWriteRawFilter) RecvMsg(m any) error { + for { + var ge gripql.RawJson + err := bw.SS.RecvMsg(&ge) + if err != nil { + return err + } + err = bw.Access.Enforce(bw.User, ge.Graph, Write) + if err == nil { + mPtr := m.(*gripql.RawJson) + *mPtr = ge + return nil + } else { + log.Infof("Graph write error: %s", ge.Graph) + } + } +} diff --git a/accounts/interface.go b/accounts/interface.go index d64d938b7..e75fbc3f2 100644 --- a/accounts/interface.go +++ b/accounts/interface.go @@ -33,17 +33,21 @@ var MethodMap = map[string]Operation{ "/gripql.Job/ViewJob": Read, "/gripql.Job/ResumeJob": Exec, - "/gripql.Edit/AddVertex": Write, - "/gripql.Edit/AddEdge": Write, - "/gripql.Edit/BulkAdd": Write, - "/gripql.Edit/AddGraph": Write, - "/gripql.Edit/DeleteGraph": Write, - "/gripql.Edit/DeleteVertex": Write, - "/gripql.Edit/DeleteEdge": Write, - "/gripql.Edit/AddIndex": Write, - "/gripql.Edit/AddSchema": Write, - "/gripql.Edit/AddMapping": Write, - "/gripql.Edit/SampleSchema": Write, //Maybe exec? + "/gripql.Edit/AddVertex": Write, + "/gripql.Edit/AddEdge": Write, + "/gripql.Edit/BulkAdd": Write, + "/gripql.Edit/BulkAddRaw": Write, + "/gripql.Edit/BulkDelete": Write, + "/gripql.Edit/AddGraph": Write, + "/gripql.Edit/DeleteGraph": Write, + "/gripql.Edit/DeleteVertex": Write, + "/gripql.Edit/DeleteEdge": Write, + "/gripql.Edit/AddIndex": Write, + "/gripql.Edit/DeleteIndex": Write, + "/gripql.Edit/AddSchema": Write, + "/gripql.Edit/AddJsonSchema": Write, + "/gripql.Edit/AddMapping": Write, + "/gripql.Edit/SampleSchema": Write, //Maybe exec? "/gripql.Configure/StartPlugin": Admin, "/gripql.Configure/ListPlugin": Admin, diff --git a/accounts/stream_out_wrapper.go b/accounts/stream_out_wrapper.go index 617624636..bba77f7c2 100644 --- a/accounts/stream_out_wrapper.go +++ b/accounts/stream_out_wrapper.go @@ -37,11 +37,11 @@ func (bw *StreamOutWrapper[X]) Context() context.Context { return bw.SS.Context() } -func (bw *StreamOutWrapper[X]) SendMsg(m interface{}) error { +func (bw *StreamOutWrapper[X]) SendMsg(m any) error { return bw.SS.SendMsg(m) } -func (bw *StreamOutWrapper[X]) RecvMsg(m interface{}) error { +func (bw *StreamOutWrapper[X]) RecvMsg(m any) error { mPtr := m.(*X) *mPtr = bw.Request return nil diff --git a/accounts/util.go b/accounts/util.go index 25d81cf26..a201376d6 100644 --- a/accounts/util.go +++ b/accounts/util.go @@ -53,7 +53,7 @@ func (c *Config) StreamInterceptor() grpc.StreamServerInterceptor { // Return a new interceptor function that authorizes RPCs // using a password stored in the config. func unaryAuthInterceptor(auth Authenticate, access Access) grpc.UnaryServerInterceptor { - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { //fmt.Printf("AuthInt: %#v\n", ctx) md, _ := metadata.FromIncomingContext(ctx) //fmt.Printf("Metadata: %#v\n", md) @@ -88,7 +88,7 @@ func unaryAuthInterceptor(auth Authenticate, access Access) grpc.UnaryServerInte // Return a new interceptor function that authorizes RPCs // using a password stored in the config. func streamAuthInterceptor(auth Authenticate, access Access) grpc.StreamServerInterceptor { - return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { //fmt.Printf("Streaming query: %#v\n", info) md, _ := metadata.FromIncomingContext(ss.Context()) @@ -141,6 +141,10 @@ func streamAuthInterceptor(auth Authenticate, access Access) grpc.StreamServerIn //stream URL formatting, each write request can //reference a different graph return handler(srv, &BulkWriteFilter{ss, user, access}) + } else if info.FullMethod == "/gripql.Edit/BulkDelete" { + return handler(srv, &BulkWriteFilter{ss, user, access}) + } else if info.FullMethod == "/gripql.Edit/BulkAddRaw" { + return handler(srv, &BulkWriteRawFilter{ss, user, access}) } else { log.Errorf("Unknown input streaming op %#v!!!", info) return handler(srv, ss) @@ -151,7 +155,7 @@ func streamAuthInterceptor(auth Authenticate, access Access) grpc.StreamServerIn } } -func getUnaryRequestGraph(req interface{}, info *grpc.UnaryServerInfo) (string, error) { +func getUnaryRequestGraph(req any, info *grpc.UnaryServerInfo) (string, error) { switch info.FullMethod { case "/gripql.Query/Traversal", "/gripql.Job/Submit", "/gripql.Job/SearchJobs": @@ -187,9 +191,15 @@ func getUnaryRequestGraph(req interface{}, info *grpc.UnaryServerInfo) (string, case "/gripql.Edit/AddSchema", "/gripql.Edit/AddMapping": o := req.(*gripql.Graph) return o.Graph, nil + case "/gripql.Edit/AddJsonSchema": + o := req.(*gripql.RawJson) + return o.Graph, nil case "/gripql.Edit/SampleSchema": o := req.(*gripql.GraphID) return o.Graph, nil + case "/gripql.Edit/BulkDelete": + o := req.(*gripql.DeleteData) + return o.Graph, nil case "/gripql.Configure/StartPlugin", "/gripql.Configure/ListPlugins", "/gripql.Configure/ListDrivers": return "*", nil //these operations effect all graphs } diff --git a/benchmark/engine_test.go b/benchmark/engine_test.go index 5882b87ec..dbc1197c6 100644 --- a/benchmark/engine_test.go +++ b/benchmark/engine_test.go @@ -22,8 +22,8 @@ func BenchmarkBaselineV(b *testing.B) { } for i := 0; i < 1000; i++ { - gid := fmt.Sprintf("v-%d", i) - db.AddVertex([]*gdbi.Vertex{{ID: gid, Label: "Vert"}}) + id := fmt.Sprintf("v-%d", i) + db.AddVertex([]*gdbi.Vertex{{ID: id, Label: "Vert"}}) } q := gripql.V() diff --git a/benchmark/kv-query-profile/main.go b/benchmark/kv-query-profile/main.go index 234b3577f..c982e8619 100644 --- a/benchmark/kv-query-profile/main.go +++ b/benchmark/kv-query-profile/main.go @@ -8,7 +8,6 @@ import ( "log" "os" "runtime/pprof" - "strings" "time" "github.com/bmeg/grip/engine/pipeline" @@ -17,7 +16,7 @@ import ( "github.com/bmeg/grip/kvgraph" "github.com/bmeg/grip/kvi" "github.com/dop251/goja" - "github.com/golang/protobuf/jsonpb" + "google.golang.org/protobuf/encoding/protojson" gripqljs "github.com/bmeg/grip/gripql/javascript" @@ -76,7 +75,7 @@ func main() { } query := gripql.GraphQuery{} - err = jsonpb.Unmarshal(strings.NewReader(string(queryJSON)), &query) + err = protojson.Unmarshal(queryJSON, &query) if err != nil { log.Printf("%s", err) return diff --git a/cmd/delete/main.go b/cmd/delete/main.go new file mode 100644 index 000000000..2f8810ffe --- /dev/null +++ b/cmd/delete/main.go @@ -0,0 +1,89 @@ +package delete + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/bmeg/grip/gripql" + "github.com/bmeg/grip/log" + "github.com/bmeg/grip/util/rpc" + "github.com/spf13/cobra" +) + +var host = "localhost:8202" +var file string +var edges []string +var vertices []string +var graph string +var data Data + +type Data struct { + Graph string `json:"graph"` + Edges []string `json:"edges"` + Vertices []string `json:"vertices"` +} + +// Cmd command line declaration +var Cmd = &cobra.Command{ + Use: "delete ", + Short: "Delete data from a graph", + Long: `JSON File Format: { + "graph": 'graph_name', + "edges":['list of edge ids'], + "vertices":['list of vertice ids'] +} + +comma delimited --edges or --vertices arguments are also supported ex: +--edges="edge1,edge2"`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if file == "" && edges == nil && vertices == nil { + return fmt.Errorf("no input file path or --edges or --vertices arg was provided") + } + + conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) + if err != nil { + return err + } + graph = args[0] + + if file != "" { + jsonFile, err := os.Open(file) + if err != nil { + log.Errorf("Failed to open file: %s", err) + } + defer jsonFile.Close() + + // Read the JSON file + byteValue, err := io.ReadAll(jsonFile) + if err != nil { + log.Errorf("Failed to read file: %s", err) + } + + // Unmarshal the JSON into the Data struct + err = json.Unmarshal(byteValue, &data) + if err != nil { + log.Errorf("Failed to unmarshal JSON: %s", err) + } + } else if edges != nil || vertices != nil { + data.Edges = edges + data.Vertices = vertices + } + + log.WithFields(log.Fields{"graph": graph}).Info("deleting data") + log.Info("VALUE OF DATA: ", data.Edges, data.Vertices) + conn.BulkDelete(&gripql.DeleteData{Graph: graph, Vertices: data.Vertices, Edges: data.Edges}) + + return nil + }, +} + +func init() { + flags := Cmd.Flags() + flags.StringVar(&host, "host", host, "grip server url") + flags.StringSliceVar(&edges, "edges", edges, "grip edges list") + flags.StringSliceVar(&vertices, "vertices", vertices, "grip vertices list") + flags.StringVar(&file, "file", file, "file name") +} diff --git a/cmd/dump/main.go b/cmd/dump/main.go index dbaa6cf26..39724ad1a 100644 --- a/cmd/dump/main.go +++ b/cmd/dump/main.go @@ -1,12 +1,12 @@ package dump import ( + "context" "fmt" "github.com/bmeg/grip/gripql" "github.com/bmeg/grip/util/rpc" "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/protojson" ) var host = "localhost:8202" @@ -27,14 +27,16 @@ var Cmd = &cobra.Command{ return err } + fm := gripql.NewFlattenMarshaler() + if vertexDump { q := gripql.V() - elems, err := conn.Traversal(&gripql.GraphQuery{Graph: graph, Query: q.Statements}) + elems, err := conn.Traversal(context.Background(), &gripql.GraphQuery{Graph: graph, Query: q.Statements}) if err != nil { return err } for v := range elems { - txt, err := protojson.Marshal(v.GetVertex()) + txt, err := fm.Marshal(v.GetVertex()) if err != nil { return err } @@ -43,13 +45,13 @@ var Cmd = &cobra.Command{ } if edgeDump { - q := gripql.E() - elems, err := conn.Traversal(&gripql.GraphQuery{Graph: graph, Query: q.Statements}) + q := gripql.V().OutE() + elems, err := conn.Traversal(context.Background(), &gripql.GraphQuery{Graph: graph, Query: q.Statements}) if err != nil { return err } for v := range elems { - txt, err := protojson.Marshal(v.GetEdge()) + txt, err := fm.Marshal(v.GetEdge()) if err != nil { return err } diff --git a/cmd/info/main.go b/cmd/info/main.go index 6dccf0827..b2cf0bd4b 100644 --- a/cmd/info/main.go +++ b/cmd/info/main.go @@ -1,6 +1,7 @@ package info import ( + "context" "fmt" "github.com/bmeg/grip/gripql" @@ -27,7 +28,7 @@ var Cmd = &cobra.Command{ fmt.Printf("Graph: %s\n", graph) q := gripql.V().Count() - res, err := conn.Traversal(&gripql.GraphQuery{Graph: graph, Query: q.Statements}) + res, err := conn.Traversal(context.Background(), &gripql.GraphQuery{Graph: graph, Query: q.Statements}) if err != nil { return err } @@ -35,8 +36,8 @@ var Cmd = &cobra.Command{ fmt.Printf("Vertex Count: %v\n", row.GetCount()) } - q = gripql.E().Count() - res, err = conn.Traversal(&gripql.GraphQuery{Graph: graph, Query: q.Statements}) + q = gripql.V().OutE().Count() + res, err = conn.Traversal(context.Background(), &gripql.GraphQuery{Graph: graph, Query: q.Statements}) if err != nil { return err } diff --git a/cmd/job/main.go b/cmd/job/main.go index e70a28358..b3783e05f 100644 --- a/cmd/job/main.go +++ b/cmd/job/main.go @@ -1,17 +1,14 @@ package job - import ( "fmt" - "encoding/json" + "github.com/bmeg/grip/gripql" + gripqljs "github.com/bmeg/grip/gripql/javascript" + _ "github.com/bmeg/grip/jsengine/goja" // import goja so it registers with the driver map "github.com/bmeg/grip/util/rpc" "github.com/spf13/cobra" - _ "github.com/bmeg/grip/jsengine/goja" // import goja so it registers with the driver map "google.golang.org/protobuf/encoding/protojson" - "github.com/dop251/goja" - gripqljs "github.com/bmeg/grip/gripql/javascript" - "github.com/bmeg/grip/jsengine/underscore" ) var host = "localhost:8202" @@ -28,7 +25,7 @@ var listJobsCmd = &cobra.Command{ Long: ``, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - graph := args[0] + graph := args[0] conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) if err != nil { @@ -48,13 +45,13 @@ var listJobsCmd = &cobra.Command{ } var dropCmd = &cobra.Command{ - Use: "drop", - Short: "List graphs", + Use: "drop ", + Short: "Drop job", Long: ``, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - graph := args[0] - jobID := args[1] + graph := args[0] + jobID := args[1] conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) if err != nil { @@ -65,19 +62,19 @@ var dropCmd = &cobra.Command{ if err != nil { return err } - fmt.Printf("%s\n", resp) + fmt.Printf("%s\n", resp) return nil }, } var getCmd = &cobra.Command{ - Use: "get job", + Use: "get ", Short: "Get job info", Long: ``, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - graph := args[0] - jobID := args[1] + graph := args[0] + jobID := args[1] conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) if err != nil { @@ -103,60 +100,94 @@ var getCmd = &cobra.Command{ }, } - var submitCmd = &cobra.Command{ Use: "submit ", Short: "Submit query job", Long: ``, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - graph := args[0] + graph := args[0] queryString := args[1] - vm := goja.New() - us, err := underscore.Asset("underscore.js") + query, err := gripqljs.ParseQuery(queryString) if err != nil { - return fmt.Errorf("failed to load underscore.js") - } - if _, err := vm.RunString(string(us)); err != nil { return err } - gripqlString, err := gripqljs.Asset("gripql.js") + query.Graph = graph + + conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) if err != nil { - return fmt.Errorf("failed to load gripql.js") + return err } - if _, err := vm.RunString(string(gripqlString)); err != nil { + + res, err := conn.Submit(query) + if err != nil { return err } - val, err := vm.RunString(queryString) + fmt.Printf("%s\n", res) + return nil + }, +} + +var resumeCmd = &cobra.Command{ + Use: "resume ", + Short: "Resume query job", + Long: ``, + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + graph := args[0] + jobID := args[1] + queryString := args[2] + + query, err := gripqljs.ParseQuery(queryString) if err != nil { return err } + query.Graph = graph - queryJSON, err := json.Marshal(val) + conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) if err != nil { return err } - query := gripql.GraphQuery{} - err = protojson.Unmarshal(queryJSON, &query) + res, err := conn.ResumeJob(graph, jobID, query) if err != nil { return err } - query.Graph = graph + + for row := range res { + rowString, _ := protojson.Marshal(row) + fmt.Printf("%s\n", rowString) + } + return nil + + }, +} + +var viewCmd = &cobra.Command{ + Use: "view ", + Short: "Resume query job", + Long: ``, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + graph := args[0] + jobID := args[1] conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) if err != nil { return err } - res, err := conn.Submit(&query) + res, err := conn.ViewJob(graph, jobID) if err != nil { return err } - fmt.Printf("%s\n", res) + for row := range res { + rowString, _ := protojson.Marshal(row) + fmt.Printf("%s\n", rowString) + } return nil }, } @@ -164,9 +195,15 @@ var submitCmd = &cobra.Command{ func init() { listJobsCmd.Flags().StringVar(&host, "host", host, "grip server url") getCmd.Flags().StringVar(&host, "host", host, "grip server url") + viewCmd.Flags().StringVar(&host, "host", host, "grip server url") dropCmd.Flags().StringVar(&host, "host", host, "grip server url") + submitCmd.Flags().StringVar(&host, "host", host, "grip server url") + resumeCmd.Flags().StringVar(&host, "host", host, "grip server url") Cmd.AddCommand(listJobsCmd) Cmd.AddCommand(getCmd) + Cmd.AddCommand(viewCmd) Cmd.AddCommand(dropCmd) + Cmd.AddCommand(submitCmd) + Cmd.AddCommand(resumeCmd) } diff --git a/cmd/jsonload/main.go b/cmd/jsonload/main.go new file mode 100644 index 000000000..1bf6ecb76 --- /dev/null +++ b/cmd/jsonload/main.go @@ -0,0 +1,88 @@ +package jsonload + +import ( + "encoding/json" + + "github.com/bmeg/grip/gripql" + "github.com/bmeg/grip/log" + "github.com/bmeg/grip/util" + "github.com/bmeg/grip/util/rpc" + "github.com/spf13/cobra" +) + +var host = "localhost:8202" +var NdJsonFile string +var workerCount = 5 +var graph string +var ExtraArgs string +var logRate = 10000 + +var Cmd = &cobra.Command{ + Use: "jsonload ", + Short: "Load, Validate NdJson data into grip graph", + Long: ``, + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + NdJsonFile = args[0] + graph = args[1] + ExtraArgs = args[2] + + var args_map map[string]any + if ExtraArgs != "" { + err := json.Unmarshal([]byte(ExtraArgs), &args_map) + if err != nil { + return err + } + } + + conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) + if err != nil { + return err + } + resp, err := conn.ListGraphs() + if err != nil { + return err + } + found := false + for _, g := range resp.Graphs { + if graph == g { + found = true + } + } + if !found { + log.WithFields(log.Fields{"graph": graph}).Info("creating graph") + err := conn.AddGraph(graph) + if err != nil { + return err + } + } + elemChan := make(chan *gripql.RawJson) + wait := make(chan bool) + go func() { + _, err := conn.BulkAddRaw(elemChan) + if err != nil { + log.Errorf("bulk add raw error: %v", err) + } + wait <- false + }() + + jsonChan, err := util.StreamRawJsonFromFile(NdJsonFile, workerCount, graph, args_map) + if err != nil { + return err + } + count := 0 + for j := range jsonChan { + count++ + if count%logRate == 0 { + log.Infof("Loaded %d vertices", count) + } + elemChan <- j + } + + close(elemChan) + <-wait + + log.WithFields(log.Fields{"graph": graph}).Info("loading data") + return nil + }, +} diff --git a/cmd/load/main.go b/cmd/load/main.go index b2e18422f..dfc7297c1 100644 --- a/cmd/load/main.go +++ b/cmd/load/main.go @@ -6,6 +6,7 @@ import ( "github.com/bmeg/grip/cmd/load/example" "github.com/bmeg/grip/gripql" "github.com/bmeg/grip/log" + "github.com/bmeg/grip/schema" "github.com/bmeg/grip/util" "github.com/bmeg/grip/util/rpc" "github.com/spf13/cobra" @@ -101,8 +102,8 @@ var Cmd = &cobra.Command{ if count%logRate == 0 { log.Infof("Loaded %d edges", count) } - if edgeUID && e.Gid == "" { - e.Gid = util.UUID() + if edgeUID && e.Id == "" { + e.Id = util.UUID() } elemChan <- &gripql.GraphElement{Graph: graph, Edge: e} } @@ -140,8 +141,8 @@ var Cmd = &cobra.Command{ if edgeCount%logRate == 0 { log.Infof("Loaded %d edges", edgeCount) } - if edgeUID && e.Gid == "" { - e.Gid = util.UUID() + if edgeUID && e.Id == "" { + e.Id = util.UUID() } elemChan <- &gripql.GraphElement{Graph: graph, Edge: e} } @@ -153,7 +154,7 @@ var Cmd = &cobra.Command{ if jsonFile != "" { log.Infof("Loading json file: %s", jsonFile) - graphs, err := gripql.ParseJSONGraphsFile(jsonFile) + graphs, err := schema.ParseJSONGraphsFile(jsonFile) if err != nil { return err } @@ -171,7 +172,7 @@ var Cmd = &cobra.Command{ if yamlFile != "" { log.Infof("Loading YAML file: %s", yamlFile) - graphs, err := gripql.ParseYAMLGraphsFile(yamlFile) + graphs, err := schema.ParseYAMLGraphsFile(yamlFile) if err != nil { return err } diff --git a/cmd/mapping/main.go b/cmd/mapping/main.go index f4e8605af..7c953a1e7 100644 --- a/cmd/mapping/main.go +++ b/cmd/mapping/main.go @@ -2,18 +2,17 @@ package mapping import ( "fmt" - "io/ioutil" + "io" "os" "github.com/bmeg/grip/gripql" + graphSchema "github.com/bmeg/grip/schema" "github.com/bmeg/grip/util/rpc" "github.com/spf13/cobra" ) var host = "localhost:8202" -var yaml = false var jsonFile string -var yamlFile string var sampleCount uint32 = 50 var excludeLabels []string @@ -42,11 +41,7 @@ var getCmd = &cobra.Command{ } var txt string - if yaml { - txt, err = gripql.GraphToYAMLString(schema) - } else { - txt, err = gripql.GraphToJSONString(schema) - } + txt, err = graphSchema.GraphToJSONString(schema) if err != nil { return err } @@ -61,7 +56,7 @@ var postCmd = &cobra.Command{ Long: ``, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - if jsonFile == "" && yamlFile == "" { + if jsonFile == "" { return fmt.Errorf("no schema file was provided") } @@ -74,13 +69,13 @@ var postCmd = &cobra.Command{ var graphs []*gripql.Graph var err error if jsonFile == "-" { - bytes, err := ioutil.ReadAll(os.Stdin) + bytes, err := io.ReadAll(os.Stdin) if err != nil { return err } - graphs, err = gripql.ParseJSONGraphs(bytes) + graphs, err = graphSchema.ParseJSONGraphs(bytes) } else { - graphs, err = gripql.ParseJSONGraphsFile(jsonFile) + graphs, err = graphSchema.ParseJSONGraphsFile(jsonFile) } if err != nil { return err @@ -93,28 +88,6 @@ var postCmd = &cobra.Command{ } } - if yamlFile != "" { - var graphs []*gripql.Graph - var err error - if jsonFile == "-" { - bytes, err := ioutil.ReadAll(os.Stdin) - if err != nil { - return err - } - graphs, err = gripql.ParseYAMLGraphs(bytes) - } else { - graphs, err = gripql.ParseYAMLGraphsFile(yamlFile) - } - if err != nil { - return err - } - for _, g := range graphs { - err := conn.AddMapping(g) - if err != nil { - return err - } - } - } return nil }, } @@ -122,12 +95,10 @@ var postCmd = &cobra.Command{ func init() { gflags := getCmd.Flags() gflags.StringVar(&host, "host", host, "grip server url") - gflags.BoolVar(&yaml, "yaml", yaml, "output schema in YAML rather than JSON format") pflags := postCmd.Flags() pflags.StringVar(&host, "host", host, "grip server url") pflags.StringVar(&jsonFile, "json", "", "JSON graph file") - pflags.StringVar(&yamlFile, "yaml", "", "YAML graph file") Cmd.AddCommand(getCmd) Cmd.AddCommand(postCmd) diff --git a/cmd/mongoload/main.go b/cmd/mongoload/main.go index 6607fb7af..673129799 100644 --- a/cmd/mongoload/main.go +++ b/cmd/mongoload/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sync" + "sync/atomic" //"io" //"strings" @@ -29,23 +30,27 @@ var edgeFile string var dirPath string var edgeUID bool -var bulkBufferSize = 1000 +var bulkBufferSize = 5_000 var workerCount = 1 -var logRate = 10000 +var logRate = 10_000 var createGraph = false func vertexSerialize(vertChan chan *gripql.Vertex, workers int) chan []byte { + var serializeCount atomic.Int64 dataChan := make(chan []byte, workers) var wg sync.WaitGroup - for i := 0; i < workers; i++ { + for range workers { wg.Add(1) go func() { for v := range vertChan { doc := mongo.PackVertex(gdbi.NewElementFromVertex(v)) rawBytes, err := bson.Marshal(doc) if err == nil { + if serializeCount.Add(1)%10000 == 0 { + log.Infof("Serialized %d vertices", serializeCount.Load()) + } dataChan <- rawBytes } } @@ -62,12 +67,12 @@ func vertexSerialize(vertChan chan *gripql.Vertex, workers int) chan []byte { func edgeSerialize(edgeChan chan *gripql.Edge, workers int) chan []byte { dataChan := make(chan []byte, workers) var wg sync.WaitGroup - for i := 0; i < workers; i++ { + for range workers { wg.Add(1) go func() { for e := range edgeChan { - if edgeUID && e.Gid == "" { - e.Gid = util.UUID() + if edgeUID && e.Id == "" { + e.Id = util.UUID() } doc := mongo.PackEdge(gdbi.NewElementFromEdge(e)) rawBytes, err := bson.Marshal(doc) @@ -100,11 +105,7 @@ var Cmd = &cobra.Command{ // Connect to mongo and start the bulk load process log.Infof("Loading data into graph: %s", graph) - client, err := mgo.NewClient(options.Client().ApplyURI(mongoHost)) - if err != nil { - return err - } - err = client.Connect(context.TODO()) + client, err := mgo.Connect(context.Background(), options.Client().ApplyURI(mongoHost)) if err != nil { return err } @@ -121,24 +122,32 @@ var Cmd = &cobra.Command{ if vertexFile != "" { log.Infof("Loading vertex file: %s", vertexFile) - vertInserter := db.NewUnorderedBufferedBulkInserter(vertexCol, bulkBufferSize). - SetBypassDocumentValidation(true). - SetOrdered(false). - SetUpsert(true) vertChan, err := util.StreamVerticesFromFile(vertexFile, workerCount) if err != nil { return err } dataChan := vertexSerialize(vertChan, workerCount) - count := 0 - for d := range dataChan { - vertInserter.InsertRaw(d) - if count%logRate == 0 { - log.Infof("Loaded %d vertices", count) - } - count++ + var wg sync.WaitGroup + var vertexCount atomic.Int64 + for range workerCount { + wg.Add(1) + go func() { + defer wg.Done() + inserter := db.NewUnorderedBufferedBulkInserter(vertexCol, bulkBufferSize). + SetBypassDocumentValidation(true). + SetOrdered(false). + SetUpsert(false) + defer inserter.Flush() + for d := range dataChan { + inserter.InsertRaw(d) + newCount := vertexCount.Add(1) + if newCount%10000 == 0 { + log.Infof("Processed %d vertices...", newCount) + } + } + }() } - vertInserter.Flush() + wg.Wait() } if edgeFile != "" { diff --git a/cmd/query/main.go b/cmd/query/main.go index f2474d7d2..13d427d0e 100644 --- a/cmd/query/main.go +++ b/cmd/query/main.go @@ -1,21 +1,20 @@ package query import ( - "encoding/json" + "context" "fmt" "github.com/bmeg/grip/gripql" gripqljs "github.com/bmeg/grip/gripql/javascript" _ "github.com/bmeg/grip/jsengine/goja" // import goja so it registers with the driver map _ "github.com/bmeg/grip/jsengine/otto" // import otto so it registers with the driver map - "github.com/bmeg/grip/jsengine/underscore" + "github.com/bmeg/grip/log" "github.com/bmeg/grip/util/rpc" - "github.com/dop251/goja" "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/protojson" ) var host = "localhost:8202" +var verbose bool // Cmd is the declaration of the command line var Cmd = &cobra.Command{ @@ -26,56 +25,42 @@ Example: grip query example-graph 'V().hasLabel("Variant").out().limit(5)'`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - vm := goja.New() - - us, err := underscore.Asset("underscore.js") - if err != nil { - return fmt.Errorf("failed to load underscore.js") - } - if _, err := vm.RunString(string(us)); err != nil { - return err - } - - gripqlString, err := gripqljs.Asset("gripql.js") - if err != nil { - return fmt.Errorf("failed to load gripql.js") - } - if _, err := vm.RunString(string(gripqlString)); err != nil { - return err - } - + graph := args[0] queryString := args[1] - val, err := vm.RunString(queryString) - if err != nil { - return err - } - queryJSON, err := json.Marshal(val) - if err != nil { - return err + if verbose { + c := log.DefaultLoggerConfig() + c.Level = "debug" + log.ConfigureLogger(c) } - query := gripql.GraphQuery{} - err = protojson.Unmarshal(queryJSON, &query) + query, err := gripqljs.ParseQuery(queryString) if err != nil { + log.Errorf("Parse error: %s", err) return err } - query.Graph = args[0] - + query.Graph = graph conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) if err != nil { + log.Errorf("Connect error: %s", err) return err } - res, err := conn.Traversal(&query) + log.Debugf("Query: %s\n", query.String()) + res, err := conn.Traversal(context.Background(), query) if err != nil { + log.Errorf("Traversal error: %s", err) return err } + fm := gripql.NewFlattenMarshaler() + count := uint64(0) for row := range res { - rowString, _ := protojson.Marshal(row) + rowString, _ := fm.Marshal(row) fmt.Printf("%s\n", rowString) + count++ } + log.Debugf("rows returned: %d", count) return nil }, } @@ -83,4 +68,6 @@ Example: func init() { flags := Cmd.Flags() flags.StringVar(&host, "host", host, "grip server url") + flags.BoolVar(&verbose, "verbose", verbose, "Verbose") + } diff --git a/cmd/rdf/main.go b/cmd/rdf/main.go index e1ccc5f73..1212b1280 100644 --- a/cmd/rdf/main.go +++ b/cmd/rdf/main.go @@ -108,7 +108,7 @@ func stringClean(cMap map[string]string, s string) string { return s } -//LoadRDFCmd is the main command line for loading RDF data +// LoadRDFCmd is the main command line for loading RDF data func LoadRDFCmd(cmd *cobra.Command, args []string) error { graph = args[0] log.Infof("Loading data into graph: %s", graph) @@ -165,12 +165,12 @@ func LoadRDFCmd(cmd *cobra.Command, args []string) error { curSubj = subj if triple.Obj.Type() == rdf.TermLiteral { if curVertex == nil { - curVertex = &gripql.Vertex{Gid: subj} + curVertex = &gripql.Vertex{Id: subj} } curVertex.SetProperty(stringClean(uMap, triple.Pred.String()), triple.Obj.String()) } else if triple.Pred.String() == RdfType { if curVertex == nil { - curVertex = &gripql.Vertex{Gid: subj} + curVertex = &gripql.Vertex{Id: subj} } curVertex.Label = stringClean(uMap, triple.Obj.String()) } else { @@ -189,7 +189,7 @@ func LoadRDFCmd(cmd *cobra.Command, args []string) error { }() for element := range elementChan { if element.vertex != nil { - if element.vertex.Gid != "" && element.vertex.Label != "" { + if element.vertex.Id != "" && element.vertex.Label != "" { err := emit.AddVertex(graph, element.vertex) if err != nil { log.Errorf("%s", err) diff --git a/cmd/root.go b/cmd/root.go index caf89a00d..b2a85c8d4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,20 +7,22 @@ import ( "os" "github.com/bmeg/grip/cmd/create" + "github.com/bmeg/grip/cmd/delete" "github.com/bmeg/grip/cmd/drop" "github.com/bmeg/grip/cmd/dump" "github.com/bmeg/grip/cmd/erclient" "github.com/bmeg/grip/cmd/info" + "github.com/bmeg/grip/cmd/job" + "github.com/bmeg/grip/cmd/jsonload" "github.com/bmeg/grip/cmd/kvload" "github.com/bmeg/grip/cmd/list" "github.com/bmeg/grip/cmd/load" - "github.com/bmeg/grip/cmd/job" + "github.com/bmeg/grip/cmd/mapping" "github.com/bmeg/grip/cmd/mongoload" "github.com/bmeg/grip/cmd/plugin" "github.com/bmeg/grip/cmd/query" "github.com/bmeg/grip/cmd/rdf" "github.com/bmeg/grip/cmd/schema" - "github.com/bmeg/grip/cmd/mapping" "github.com/bmeg/grip/cmd/server" "github.com/bmeg/grip/cmd/stream" "github.com/bmeg/grip/cmd/version" @@ -59,6 +61,7 @@ func init() { RootCmd.AddCommand(job.Cmd) RootCmd.AddCommand(load.Cmd) RootCmd.AddCommand(mongoload.Cmd) + RootCmd.AddCommand(jsonload.Cmd) RootCmd.AddCommand(query.Cmd) RootCmd.AddCommand(erclient.Cmd) RootCmd.AddCommand(rdf.Cmd) @@ -69,6 +72,8 @@ func init() { RootCmd.AddCommand(plugin.Cmd) RootCmd.AddCommand(version.Cmd) RootCmd.AddCommand(kvload.Cmd) + RootCmd.AddCommand(delete.Cmd) + } var genBashCompletionCmd = &cobra.Command{ diff --git a/cmd/schema/main.go b/cmd/schema/main.go index fb3c570db..4ddce767e 100644 --- a/cmd/schema/main.go +++ b/cmd/schema/main.go @@ -2,24 +2,18 @@ package schema import ( "fmt" - "io/ioutil" - "os" "github.com/bmeg/grip/gripql" - gripql_schema "github.com/bmeg/grip/gripql/schema" "github.com/bmeg/grip/log" + "github.com/bmeg/grip/schema" "github.com/bmeg/grip/util/rpc" "github.com/spf13/cobra" ) var host = "localhost:8202" -var yaml = false var jsonFile string -var yamlFile string -var sampleCount uint32 = 50 -var excludeLabels []string - -var manual bool +var graphName string +var jsonSchemaFile string // Cmd line declaration var Cmd = &cobra.Command{ @@ -40,17 +34,14 @@ var getCmd = &cobra.Command{ return err } - schema, err := conn.GetSchema(graph) + gripqlschema, err := conn.GetSchema(graph) if err != nil { return err } var txt string - if yaml { - txt, err = gripql.GraphToYAMLString(schema) - } else { - txt, err = gripql.GraphToJSONString(schema) - } + + txt, err = schema.GraphToJSONString(gripqlschema) if err != nil { return err } @@ -59,13 +50,18 @@ var getCmd = &cobra.Command{ }, } +type Config struct { + DependencyOrder []string `yaml:"dependency_order"` +} + var postCmd = &cobra.Command{ - Use: "post", - Short: "Post graph schemas", + Use: "post [graph name]", + Short: "Post jsonschema graph schemas", Long: ``, - Args: cobra.NoArgs, + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - if jsonFile == "" && yamlFile == "" { + graphName := args[0] + if jsonFile == "" && jsonSchemaFile == "" { return fmt.Errorf("no schema file was provided") } @@ -74,42 +70,35 @@ var postCmd = &cobra.Command{ return err } - if jsonFile != "" { - var graphs []*gripql.Graph - var err error - if jsonFile == "-" { - bytes, err := ioutil.ReadAll(os.Stdin) - if err != nil { - return err + /* + * Deprecate this for now. This expects a schema in the legacy grip schema graph format + * if jsonFile != "" { + var graphs []*gripql.Graph + var err error + if jsonFile == "-" { + bytes, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + graphs, err = schema.ParseJSONGraphs(bytes) + } else { + graphs, err = schema.ParseJSONGraphsFile(jsonFile) } - graphs, err = gripql.ParseJSONGraphs(bytes) - } else { - graphs, err = gripql.ParseJSONGraphsFile(jsonFile) - } - if err != nil { - return err - } - for _, g := range graphs { - err := conn.AddSchema(g) if err != nil { return err } - log.Debug("Posted schema: %s", g.Graph) - } - } - - if yamlFile != "" { - var graphs []*gripql.Graph - var err error - if jsonFile == "-" { - bytes, err := ioutil.ReadAll(os.Stdin) - if err != nil { - return err + for _, g := range graphs { + err := conn.AddSchema(g) + if err != nil { + return err + } + log.Debugf("Posted schema: %s", g.Graph) } - graphs, err = gripql.ParseYAMLGraphs(bytes) - } else { - graphs, err = gripql.ParseYAMLGraphsFile(yamlFile) - } + }*/ + + if jsonSchemaFile != "" { + log.Infof("Loading Json Schema file: %s", jsonSchemaFile) + graphs, err := schema.ParseJsonSchema(jsonSchemaFile, graphName) if err != nil { return err } @@ -118,70 +107,22 @@ var postCmd = &cobra.Command{ if err != nil { return err } + log.Debugf("Posted schema: %s", g.Graph) } } return nil }, } -var sampleCmd = &cobra.Command{ - Use: "sample ", - Short: "Sample graph and construct schema", - Long: ``, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - graph := args[0] - - conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) - if err != nil { - return err - } - - var schema *gripql.Graph - if manual { - schema, err = gripql_schema.ScanSchema(conn, graph, sampleCount, excludeLabels) - if err != nil { - return err - } - } else { - schema, err = conn.SampleSchema(graph) - if err != nil { - return err - } - } - var txt string - if yaml { - txt, err = gripql.GraphToYAMLString(schema) - } else { - txt, err = gripql.GraphToJSONString(schema) - } - if err != nil { - return err - } - fmt.Printf("%s\n", txt) - conn.Close() - return nil - }, -} - func init() { gflags := getCmd.Flags() gflags.StringVar(&host, "host", host, "grip server url") - gflags.BoolVar(&yaml, "yaml", yaml, "output schema in YAML rather than JSON format") pflags := postCmd.Flags() pflags.StringVar(&host, "host", host, "grip server url") - pflags.StringVar(&jsonFile, "json", "", "JSON graph file") - pflags.StringVar(&yamlFile, "yaml", "", "YAML graph file") - - sflags := sampleCmd.Flags() - sflags.StringVar(&host, "host", host, "grip server url") - sflags.Uint32Var(&sampleCount, "sample", sampleCount, "Number of elements to sample") - sflags.BoolVar(&yaml, "yaml", yaml, "output schema in YAML rather than JSON format") - sflags.BoolVar(&manual, "manual", manual, "Use client side schema sampling") - sflags.StringSliceVar(&excludeLabels, "exclude-label", excludeLabels, "exclude vertex/edge label from schema") + //pflags.StringVar(&jsonFile, "json", "", "JSON graph file") + pflags.StringVar(&jsonSchemaFile, "jsonSchema", "", "Json Schema") Cmd.AddCommand(getCmd) Cmd.AddCommand(postCmd) - Cmd.AddCommand(sampleCmd) } diff --git a/cmd/server/main.go b/cmd/server/main.go index 6707f0b37..010f299d4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/signal" + "strings" "github.com/bmeg/grip/config" "github.com/bmeg/grip/log" @@ -17,9 +18,11 @@ import ( var conf = &config.Config{} var configFile string -var driver = "badger" +var driver = "pebble" +var verbose bool var endPoints = map[string]string{} +var endPointConfig = map[string]string{} var pluginDir = "" @@ -28,7 +31,9 @@ var pluginDir = "" // This blocks indefinitely. func Run(conf *config.Config, baseDir string) error { log.ConfigureLogger(conf.Logger) - log.WithFields(log.Fields{"Config": conf}).Info("Starting Server") + cpyConf := config.DeepCopyRedactedConfig(conf) + + log.WithFields(log.Fields{"Config": cpyConf}).Info("Starting Server") ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -45,7 +50,14 @@ func Run(conf *config.Config, baseDir string) error { } for k, v := range endPoints { - if err := srv.AddEndpoint(k, v); err != nil { + c := map[string]string{} + for ck, cv := range endPointConfig { + if strings.HasPrefix(ck, k+":") { + nk := ck[len(k)+1:] + c[nk] = cv + } + } + if err := srv.AddEndpoint(k, v, c); err != nil { log.Errorf("Error loading pluging %s: %s", k, err) } } @@ -81,6 +93,10 @@ var Cmd = &cobra.Command{ dconf.AddPebbleDefault() } else if driver == "mongo" { dconf.AddMongoDefault() + } else if driver == "grids" { + dconf.AddGridsDefault() + } else if driver == "sqlite" { + dconf.AddSqliteDefault() } } if pluginDir != "" { @@ -100,6 +116,11 @@ var Cmd = &cobra.Command{ conf.RPCClient.ServerAddress = conf.Server.RPCAddress() } } + + if verbose { + conf.Logger.Level = "debug" + } + return nil }, RunE: func(cmd *cobra.Command, args []string) error { @@ -117,10 +138,13 @@ func init() { flags.StringVar(&conf.Logger.Formatter, "log-format", conf.Logger.Formatter, "Log format [text, json]") flags.BoolVar(&conf.Server.RequestLogging.Enable, "log-requests", conf.Server.RequestLogging.Enable, "Log all requests") + flags.BoolVar(&verbose, "verbose", verbose, "Verbose") + flags.StringVarP(&pluginDir, "plugins", "p", pluginDir, "Directory with GRIPPER plugins") flags.StringVarP(&driver, "driver", "d", driver, "Default Driver") flags.StringToStringVarP(&endPoints, "endpoint", "w", endPoints, "web endpoint plugins") + flags.StringToStringVarP(&endPointConfig, "endpoint-config", "l", endPointConfig, "web endpoint configuration") flags.StringToStringVarP(&conf.Sources, "er", "e", conf.Sources, "GRIPPER source address") } diff --git a/cmd/stream/main.go b/cmd/stream/main.go index 57925e2dd..e0fea5db4 100644 --- a/cmd/stream/main.go +++ b/cmd/stream/main.go @@ -1,6 +1,12 @@ package stream import ( + "bytes" + "fmt" + "regexp" + "strings" + "time" + "github.com/Shopify/sarama" "github.com/bmeg/grip/gripql" "github.com/bmeg/grip/log" @@ -9,81 +15,185 @@ import ( "google.golang.org/protobuf/encoding/protojson" ) -var kafka = "localhost:9092" var host = "localhost:8202" -var graph string -var vertexTopic = "grip_vertex" -var edgeTopic = "grip_edge" +var KafkaUsername = "" +var KafkaPassword = "" +var Topic = "" -// Cmd is the base command called by the cobra command line system +// Fetch all requests from Kafka and recreate all write +// Ex command using ci env: grip stream localhost:9092 --KafkaUsername admin --KafkaPassword adminpassword --Topic gripHistory var Cmd = &cobra.Command{ - Use: "stream ", + Use: "stream", Short: "Stream data into a graph from Kafka", Long: ``, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - graph = args[0] - log.WithFields(log.Fields{"kafka": kafka, "graph": graph}).Errorf("Streaming data from Kafka into graph") + kafkaHost := args[0] conn, err := gripql.Connect(rpc.ConfigWithDefaults(host), true) if err != nil { return err } + defer conn.Close() - consumer, err := sarama.NewConsumer([]string{kafka}, nil) - if err != nil { - panic(err) + config := sarama.NewConfig() + config.Version = sarama.V2_8_0_0 + config.Net.SASL.Enable = true + config.Net.SASL.User = KafkaUsername + config.Net.SASL.Password = KafkaPassword + config.Net.SASL.Mechanism = sarama.SASLTypePlaintext + config.Producer.Return.Successes = true + config.Net.SASL.Handshake = true + config.Net.TLS.Enable = false + if KafkaUsername == "" || KafkaPassword == "" || Topic == "" { + return fmt.Errorf("Kafka username and password and topic must be populated") } - vertexConsumer, err := consumer.ConsumePartition(vertexTopic, 0, sarama.OffsetOldest) - edgeConsumer, err := consumer.ConsumePartition(edgeTopic, 0, sarama.OffsetOldest) + consumer, err := sarama.NewConsumer([]string{kafkaHost}, config) + if err != nil { + return fmt.Errorf("failed to create Kafka consumer: %w", err) + } + defer consumer.Close() + writeConsumer, err := consumer.ConsumePartition(Topic, 0, sarama.OffsetOldest) + if err != nil { + return fmt.Errorf("failed to consume partition: %w", err) + } + defer writeConsumer.Close() done := make(chan bool) go func() { - count := 0 - for msg := range vertexConsumer.Messages() { - v := gripql.Vertex{} - err := protojson.Unmarshal(msg.Value, &v) - if err != nil { - log.WithFields(log.Fields{"error": err}).Error("vertex consumer: unmarshal error") - continue - } - err = conn.AddVertex(graph, &v) - if err != nil { - log.WithFields(log.Fields{"error": err}).Error("vertex consumer: add error") - continue - } - count++ - if count%1000 == 0 { - log.Infof("Loaded %d vertices", count) - } - } - done <- true - }() + defer close(done) + for { + // timeout after 3 seconds have elapsed and no new message has been sent to Consumer + timeout := time.NewTimer(3 * time.Second) + select { + case msg, ok := <-writeConsumer.Messages(): + if !ok { + log.Info("writeConsumer channel closed") + return + } + timeout.Stop() + Method := "" + Path := "" + for _, HeaderMsg := range msg.Headers { + if bytes.Equal(HeaderMsg.Key, []byte("PATH")) { + Path = string(HeaderMsg.Value) + } else if bytes.Equal(HeaderMsg.Key, []byte("METHOD")) { + Method = string(HeaderMsg.Value) + } + } + if Path == "" || Method == "" { + continue + } - go func() { - count := 0 - for msg := range edgeConsumer.Messages() { - e := gripql.Edge{} - err := protojson.Unmarshal(msg.Value, &e) - if err != nil { - log.WithFields(log.Fields{"error": err}).Error("edge consumer: unmarshal error") - continue - } - err = conn.AddEdge(graph, &e) - if err != nil { - log.WithFields(log.Fields{"error": err}).Error("edge consumer: add error") - continue - } - count++ - if count%1000 == 0 { - log.Infof("Loaded %d edges", count) + fmt.Println("PATH: ", Path, "METHOD: ", Method) + bulk := regexp.MustCompile("^/v1/graph$") + graph := regexp.MustCompile("^/v1/graph/([^/]+)$") + switch Method { + case "DELETE": + deleteVertex := regexp.MustCompile("^/v1/graph/([^/]+)/vertex/([^/]+)$") + deleteEdge := regexp.MustCompile("^/v1/graph/([^/]+)/edge/([^/]+)$") + switch { + case bulk.MatchString(Path): + g := gripql.DeleteData{} + err := protojson.Unmarshal(msg.Value, &g) + if err != nil { + log.WithFields(log.Fields{"error": err, "value": string(msg.Value)}).Error("consumer: bulk delete unmarshal vertex error") + continue + } + err = conn.BulkDelete(&g) + if err != nil { + log.WithFields(log.Fields{"error": err, "path": Path}).Errorf("consumer: bulk delete") + continue + } + case deleteVertex.MatchString(Path): + id := strings.Split(Path, "/") + err := conn.DeleteVertex(id[len(id)-3], id[len(id)-1]) + if err != nil { + log.WithFields(log.Fields{"error": err, "path": Path}).Errorf("consumer: delete vertex") + continue + } + case deleteEdge.MatchString(Path): + id := strings.Split(Path, "/") + err := conn.DeleteEdge(id[len(id)-3], id[len(id)-1]) + if err != nil { + log.WithFields(log.Fields{"error": err, "path": Path}).Errorf("consumer: delete edge") + continue + } + case graph.MatchString(Path): + paths := strings.Split(Path, "/") + graphName := paths[len(paths)-1] + err = conn.DeleteGraph(graphName) + if err != nil { + log.WithFields(log.Fields{"error": err, "graph": graphName}).Errorf("consumer: delete graph") + } + default: + continue + } + case "POST": + addVertex := regexp.MustCompile("^/v1/graph/([^/]+)/vertex$") + addEdge := regexp.MustCompile("^/v1/graph/([^/]+)/edge$") + addSchema := regexp.MustCompile("^/v1/graph/([^/]+)/schema$") + switch { + case bulk.MatchString(Path): + fmt.Printf("WE HERE: %s", string(msg.Value)) + case addVertex.MatchString(Path): + v := gripql.Vertex{} + err = protojson.Unmarshal(msg.Value, &v) + if err != nil { + log.WithFields(log.Fields{"error": err, "value": string(msg.Value)}).Error("consumer: unmarshal vertex error") + continue + } + id := strings.Split(Path, "/") + err = conn.AddVertex(id[len(id)-2], &v) + if err != nil { + log.WithFields(log.Fields{"error": err, "vertex": v}).Error("consumer: add vertex error") + continue + } + case addEdge.MatchString(Path): + e := gripql.Edge{} + err = protojson.Unmarshal(msg.Value, &e) + if err != nil { + log.WithFields(log.Fields{"error": err, "value": string(msg.Value)}).Error("consumer: unmarshal edge error") + continue + } + id := strings.Split(Path, "/") + err = conn.AddEdge(id[len(id)-2], &e) + if err != nil { + log.WithFields(log.Fields{"error": err, "edge": e}).Error("consumer: add edge error") + continue + } + case addSchema.MatchString(Path): + graph := gripql.Graph{} + err = protojson.Unmarshal(msg.Value, &graph) + if err != nil { + log.WithFields(log.Fields{"error": err, "value": string(msg.Value)}).Error("consumer: unmarshal schema error") + continue + } + err = conn.AddSchema(&graph) + if err != nil { + log.WithFields(log.Fields{"error": err, "schema": graph}).Error("consumer: add schema error") + } + case graph.MatchString(Path): + paths := strings.Split(Path, "/") + graphName := paths[len(paths)-1] + err = conn.AddGraph(graphName) + if err != nil { + log.WithFields(log.Fields{"error": err, "graph": graphName}).Error("consumer: add graph error") + } + default: + continue + } + default: + continue + } + case <-timeout.C: + return } } - done <- true }() - <-done + <-done return nil }, @@ -91,8 +201,9 @@ var Cmd = &cobra.Command{ func init() { flags := Cmd.Flags() - flags.StringVar(&kafka, "kafka", "localhost:9092", "Kafka server url") flags.StringVar(&host, "host", "localhost:8202", "grip server url") - flags.StringVar(&vertexTopic, "vertex", "grip_vertex", "vertex topic name") - flags.StringVar(&edgeTopic, "edge", "grip_edge", "edge topic name") + flags.StringVar(&KafkaUsername, "KafkaUsername", "", "Kafka Username") + flags.StringVar(&KafkaPassword, "KafkaPassword", "", "Kafka Password") + flags.StringVar(&Topic, "Topic", "gripHistory", "Kafka Topic") + } diff --git a/config/config.go b/config/config.go index d6c0da973..0848f4339 100644 --- a/config/config.go +++ b/config/config.go @@ -2,19 +2,19 @@ package config import ( "fmt" - "io/ioutil" "math/rand" + "os" "path/filepath" "strings" "time" - "github.com/bmeg/grip/elastic" esql "github.com/bmeg/grip/existing-sql" "github.com/bmeg/grip/gripper" - "github.com/bmeg/grip/gripql" "github.com/bmeg/grip/log" "github.com/bmeg/grip/mongo" "github.com/bmeg/grip/psql" + "github.com/bmeg/grip/schema" + "github.com/bmeg/grip/sqlite" "github.com/bmeg/grip/util" "github.com/bmeg/grip/util/duration" "github.com/bmeg/grip/util/rpc" @@ -26,16 +26,24 @@ func init() { } type DriverConfig struct { - Grids *string - Badger *string - Bolt *string - Level *string - Pebble *string - Elasticsearch *elastic.Config - MongoDB *mongo.Config - PSQL *psql.Config - ExistingSQL *esql.Config - Gripper *gripper.Config + Grids *string + Badger *string + Bolt *string + Level *string + Pebble *string + MongoDB *mongo.Config + PSQL *psql.Config + ExistingSQL *esql.Config + Sqlite *sqlite.Config + Gripper *gripper.Config +} + +type KafkaConfig struct { + Username *string + Password *string + Hostname *string + // Limit to one topic stream for now + Topic *string } // Config describes the configuration for Grip. @@ -47,6 +55,7 @@ type Config struct { Graphs map[string]string Drivers map[string]DriverConfig Sources map[string]string + Kafka KafkaConfig } type DriverParams interface { @@ -68,8 +77,8 @@ func DefaultConfig() *Config { c.Server.SchemaInspectN = 500 c.Server.SchemaRandomSample = true c.Server.RequestLogging.HeaderWhitelist = []string{ - "authorization", "oauthemail", "content-type", "content-length", - "forwarded", "x-forwarded-for", "x-forwarded-host", "user-agent", + "content-type", "content-length", "forwarded", + "x-forwarded-for", "x-forwarded-host", "user-agent", } c.RPCClient = rpc.ConfigWithDefaults(c.Server.RPCAddress()) @@ -79,6 +88,9 @@ func DefaultConfig() *Config { c.Sources = map[string]string{} c.Logger = log.DefaultLoggerConfig() + + c.Kafka = KafkaConfig{} + return c } @@ -101,6 +113,18 @@ func (conf *Config) AddMongoDefault() { conf.Default = "mongo" } +func (conf *Config) AddSqliteDefault() { + c := sqlite.Config{DBName: "grip-sqlite.db"} + conf.Drivers["sqlite"] = DriverConfig{Sqlite: &c} + conf.Default = "sqlite" +} + +func (conf *Config) AddGridsDefault() { + n := "grip-grids.db" + conf.Drivers["grids"] = DriverConfig{Grids: &n} + conf.Default = "grids" +} + // TestifyConfig randomizes ports and database paths/names func TestifyConfig(c *Config) { rand := strings.ToLower(util.RandomString(6)) @@ -117,12 +141,15 @@ func TestifyConfig(c *Config) { a := "grip.db." + rand d.Badger = &a } + if d.Pebble != nil { + a := "grip.db." + rand + d.Pebble = &a + } if d.MongoDB != nil { d.MongoDB.DBName = "gripdb-" + rand } - if d.Elasticsearch != nil { - d.Elasticsearch.DBName = "gripdb-" + rand - d.Elasticsearch.Synchronous = true + if d.Sqlite != nil { + d.Sqlite.DBName = "gripdb-" + rand } c.Drivers[c.Default] = d } @@ -132,9 +159,6 @@ func (c *Config) SetDefaults() { if d.MongoDB != nil { d.MongoDB.SetDefaults() } - if d.Elasticsearch != nil { - d.Elasticsearch.SetDefaults() - } } } @@ -169,7 +193,7 @@ func ParseConfigFile(relpath string, conf *Config) error { } // Read file - source, err := ioutil.ReadFile(path) + source, err := os.ReadFile(path) if err != nil { return fmt.Errorf("failed to read config at path %s: \n%v", path, err) } @@ -184,17 +208,17 @@ func ParseConfigFile(relpath string, conf *Config) error { if conf.Drivers[i].Gripper.MappingFile != "" { gpath := filepath.Join(filepath.Dir(path), conf.Drivers[i].Gripper.MappingFile) - gsource, err := ioutil.ReadFile(gpath) + gsource, err := os.ReadFile(gpath) if err != nil { return fmt.Errorf("failed to read graph at path %s: \n%v", gpath, err) } // Parse file - data := map[string]interface{}{} + data := map[string]any{} err = yaml.Unmarshal(gsource, &data) if err != nil { return fmt.Errorf("failed to parse config at path %s: \n%v", path, err) } - graph, err := gripql.GraphMapToProto(data) + graph, err := schema.GraphMapToProto(data) if err != nil { return fmt.Errorf("failed to parse config at path %s: \n%v", path, err) } @@ -204,3 +228,94 @@ func ParseConfigFile(relpath string, conf *Config) error { } return nil } + +func DeepCopyRedactedConfig(conf *Config) *Config { + if conf == nil { + return nil + } + + cpyConf := &Config{ + Server: conf.Server, + RPCClient: rpc.Config{ + ServerAddress: conf.RPCClient.ServerAddress, + Timeout: conf.RPCClient.Timeout, + MaxRetries: conf.RPCClient.MaxRetries, + User: conf.RPCClient.User, + Password: "[REDACTED]", + }, + Logger: conf.Logger, + Default: conf.Default, + Graphs: make(map[string]string, len(conf.Graphs)), + Drivers: make(map[string]DriverConfig, len(conf.Drivers)), + Sources: make(map[string]string, len(conf.Sources)), + Kafka: KafkaConfig{}, + } + + for k, v := range conf.Graphs { + cpyConf.Graphs[k] = v + } + + for k, v := range conf.Sources { + cpyConf.Sources[k] = v + } + + for k, driver := range conf.Drivers { + cpyDriver := DriverConfig{} + if driver.Grids != nil { + grids := *driver.Grids + cpyDriver.Grids = &grids + } + if driver.Badger != nil { + badger := *driver.Badger + cpyDriver.Badger = &badger + } + if driver.Bolt != nil { + bolt := *driver.Bolt + cpyDriver.Bolt = &bolt + } + if driver.Level != nil { + level := *driver.Level + cpyDriver.Level = &level + } + if driver.Pebble != nil { + pebble := *driver.Pebble + cpyDriver.Pebble = &pebble + } + if driver.MongoDB != nil { + mongoDB := &mongo.Config{DBName: "[REDACTED]", Password: "[REDACTED]"} + cpyDriver.MongoDB = mongoDB + } + if driver.PSQL != nil { + psql := &psql.Config{DBName: "[REDACTED]", Password: "[REDACTED]"} + cpyDriver.PSQL = psql + } + if driver.ExistingSQL != nil { + existingSQL := &esql.Config{DataSourceName: "[REDACTED]"} + cpyDriver.ExistingSQL = existingSQL + } + if driver.Sqlite != nil { + sqlite := *driver.Sqlite + cpyDriver.Sqlite = &sqlite + } + if driver.Gripper != nil { + gripper := &gripper.Config{MappingFile: "[REDACTED]"} + cpyDriver.Gripper = gripper + } + cpyConf.Drivers[k] = cpyDriver + } + if conf.Kafka.Username != nil { + cpyConf.Kafka.Username = conf.Kafka.Username + } + if conf.Kafka.Password != nil { + password := "[REDACTED]" // Redact (tagged as sensitive) + cpyConf.Kafka.Password = &password + } + if conf.Kafka.Hostname != nil { + cpyConf.Kafka.Hostname = conf.Kafka.Hostname + } + if conf.Kafka.Topic != nil { + topic := *conf.Kafka.Topic + cpyConf.Kafka.Topic = &topic + } + return cpyConf +} diff --git a/conformance/graphs/fhir.edges b/conformance/graphs/fhir.edges new file mode 100644 index 000000000..1ef87b22c --- /dev/null +++ b/conformance/graphs/fhir.edges @@ -0,0 +1,6 @@ +{"_id":"patient_a-observation_a1", "_from":"patient_a", "_to":"observation_a1", "_label":"patient_observation"} +{"_id":"patient_a-observation_a2", "_from":"patient_a", "_to":"observation_a2", "_label":"patient_observation"} +{"_id":"patient_a-observation_a3", "_from":"patient_a", "_to":"observation_a3", "_label":"patient_observation"} +{"_id":"patient_b-observation_b1", "_from":"patient_b", "_to":"observation_b1", "_label":"patient_observation"} +{"_id":"patient_b-observation_b2", "_from":"patient_b", "_to":"observation_b2", "_label":"patient_observation"} +{"_id":"patient_b-observation_b3", "_from":"patient_b", "_to":"observation_b3", "_label":"patient_observation"} diff --git a/conformance/graphs/fhir.vertices b/conformance/graphs/fhir.vertices new file mode 100644 index 000000000..c5f104e30 --- /dev/null +++ b/conformance/graphs/fhir.vertices @@ -0,0 +1,8 @@ +{"_id":"patient_a", "_label":"Patient", "name":"Alice"} +{"_id":"patient_b", "_label":"Patient", "name":"Bob"} +{"_id":"observation_a1", "_label":"Observation", "key":"age", "value":36} +{"_id":"observation_a2", "_label":"Observation", "key":"sex", "value":"Female"} +{"_id":"observation_a3", "_label":"Observation", "key":"blood_pressure", "value":"111/78"} +{"_id":"observation_b1", "_label":"Observation", "key":"age", "value":42} +{"_id":"observation_b2", "_label":"Observation", "key":"sex", "value":"Male"} +{"_id":"observation_b3", "_label":"Observation", "key":"blood_pressure", "value":"120/80"} diff --git a/conformance/graphs/prompt-schema.json b/conformance/graphs/prompt-schema.json new file mode 100644 index 000000000..4518b99c5 --- /dev/null +++ b/conformance/graphs/prompt-schema.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://grip-schema.io/schema/0.0.1", + "$defs": { + "Prompt": { + "$id": "http://grip-schema.io/schema/0.0.1/Prompt", + "additionalProperties": false, + "description": "User generated prompt", + "links": [ + { + "href": "{id}", + "rel": "responses", + "targetHints": { + "direction": [ + "outbound" + ], + "multiplicity": [ + "has_many" + ], + "regex_match": [ + "response:*" + ] + }, + "targetSchema": { + "$ref": "http://grip-schema.io/schema/0.0.1/Response" + }, + "templatePointers": { + "id": "/response" + } + } + ], + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "resourceType": { + "type": "string" + }, + "responses": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Response": { + "$id": "http://grip-schema.io/schema/0.0.1/Response", + "additionalProperties": false, + "description": "User LLM generated response", + "links": [ + { + "href": "{id}", + "rel": "prompt", + "targetHints": { + "direction": [ + "outbound" + ], + "multiplicity": [ + "has_one" + ], + "regex_match": [ + "prompt:*" + ] + }, + "targetSchema": { + "$ref": "http://grip-schema.io/schema/0.0.1/Prompt" + }, + "templatePointers": { + "id": "/prompt" + } + } + ], + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "resourceType": { + "type": "string" + }, + "prompt": { + "type": "string" + }, + "model": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/conformance/graphs/prompt.yaml b/conformance/graphs/prompt.yaml new file mode 100644 index 000000000..0f58c6f54 --- /dev/null +++ b/conformance/graphs/prompt.yaml @@ -0,0 +1,29 @@ +id: http://grip-schema.io/schema/0.0.1/Prompt +additionalProperties: false +description: User generated prompt +links: +- href: '{id}' + rel: responses + targetHints: + direction: + - outbound + multiplicity: + - has_many + regex_match: + - response:* + targetSchema: + $ref: http://grip-schema.io/schema/0.0.1/Response + templatePointers: + id: /response + +properties: + id: + type: string + text: + type: string + resourceType: + type: string + responses: + type: array + items: + type: string diff --git a/conformance/graphs/response.yaml b/conformance/graphs/response.yaml new file mode 100644 index 000000000..311ef2c5f --- /dev/null +++ b/conformance/graphs/response.yaml @@ -0,0 +1,30 @@ +id: http://grip-schema.io/schema/0.0.1/Response +additionalProperties: false +description: User LLM generated response +links: +- href: '{id}' + rel: prompt + targetHints: + direction: + - outbound + multiplicity: + - has_one + regex_match: + - prompt:* + targetSchema: + $ref: http://grip-schema.io/schema/0.0.1/Prompt + templatePointers: + id: /prompt + +properties: + id: + type: string + text: + type: string + resourceType: + type: string + prompt: + type: string + model: + type: string + diff --git a/conformance/graphs/swapi.edges b/conformance/graphs/swapi.edges index 93b33c120..410283e32 100644 --- a/conformance/graphs/swapi.edges +++ b/conformance/graphs/swapi.edges @@ -1,144 +1,144 @@ -{"gid": "Film:1-characters-Character:1", "label": "characters", "from": "Film:1", "to": "Character:1"} -{"gid": "Film:1-characters-Character:2", "label": "characters", "from": "Film:1", "to": "Character:2"} -{"gid": "Film:1-characters-Character:3", "label": "characters", "from": "Film:1", "to": "Character:3"} -{"gid": "Film:1-characters-Character:4", "label": "characters", "from": "Film:1", "to": "Character:4"} -{"gid": "Film:1-characters-Character:5", "label": "characters", "from": "Film:1", "to": "Character:5"} -{"gid": "Film:1-characters-Character:6", "label": "characters", "from": "Film:1", "to": "Character:6"} -{"gid": "Film:1-characters-Character:7", "label": "characters", "from": "Film:1", "to": "Character:7"} -{"gid": "Film:1-characters-Character:8", "label": "characters", "from": "Film:1", "to": "Character:8"} -{"gid": "Film:1-characters-Character:9", "label": "characters", "from": "Film:1", "to": "Character:9"} -{"gid": "Film:1-characters-Character:10", "label": "characters", "from": "Film:1", "to": "Character:10"} -{"gid": "Film:1-characters-Character:12", "label": "characters", "from": "Film:1", "to": "Character:12"} -{"gid": "Film:1-characters-Character:13", "label": "characters", "from": "Film:1", "to": "Character:13"} -{"gid": "Film:1-characters-Character:14", "label": "characters", "from": "Film:1", "to": "Character:14"} -{"gid": "Film:1-characters-Character:15", "label": "characters", "from": "Film:1", "to": "Character:15"} -{"gid": "Film:1-characters-Character:16", "label": "characters", "from": "Film:1", "to": "Character:16"} -{"gid": "Film:1-characters-Character:18", "label": "characters", "from": "Film:1", "to": "Character:18"} -{"gid": "Film:1-characters-Character:19", "label": "characters", "from": "Film:1", "to": "Character:19"} -{"gid": "Film:1-characters-Character:81", "label": "characters", "from": "Film:1", "to": "Character:81"} -{"gid": "Film:1-planets-Planet:2", "label": "planets", "from": "Film:1", "to": "Planet:2", "data" : {"scene_count" : 10}} -{"gid": "Film:1-planets-Planet:3", "label": "planets", "from": "Film:1", "to": "Planet:3", "data" : {"scene_count" : 15}} -{"gid": "Film:1-planets-Planet:1", "label": "planets", "from": "Film:1", "to": "Planet:1", "data" : {"scene_count" : 20}} -{"gid": "Film:1-starships-Starship:2", "label": "starships", "from": "Film:1", "to": "Starship:2"} -{"gid": "Film:1-starships-Starship:3", "label": "starships", "from": "Film:1", "to": "Starship:3"} -{"gid": "Film:1-starships-Starship:5", "label": "starships", "from": "Film:1", "to": "Starship:5"} -{"gid": "Film:1-starships-Starship:9", "label": "starships", "from": "Film:1", "to": "Starship:9"} -{"gid": "Film:1-starships-Starship:10", "label": "starships", "from": "Film:1", "to": "Starship:10"} -{"gid": "Film:1-starships-Starship:11", "label": "starships", "from": "Film:1", "to": "Starship:11"} -{"gid": "Film:1-starships-Starship:12", "label": "starships", "from": "Film:1", "to": "Starship:12"} -{"gid": "Film:1-starships-Starship:13", "label": "starships", "from": "Film:1", "to": "Starship:13"} -{"gid": "Film:1-vehicles-Vehicle:4", "label": "vehicles", "from": "Film:1", "to": "Vehicle:4"} -{"gid": "Film:1-vehicles-Vehicle:6", "label": "vehicles", "from": "Film:1", "to": "Vehicle:6"} -{"gid": "Film:1-vehicles-Vehicle:7", "label": "vehicles", "from": "Film:1", "to": "Vehicle:7"} -{"gid": "Film:1-vehicles-Vehicle:8", "label": "vehicles", "from": "Film:1", "to": "Vehicle:8"} -{"gid": "Film:1-species-Species:5", "label": "species", "from": "Film:1", "to": "Species:5"} -{"gid": "Film:1-species-Species:3", "label": "species", "from": "Film:1", "to": "Species:3"} -{"gid": "Film:1-species-Species:2", "label": "species", "from": "Film:1", "to": "Species:2"} -{"gid": "Film:1-species-Species:1", "label": "species", "from": "Film:1", "to": "Species:1"} -{"gid": "Film:1-species-Species:4", "label": "species", "from": "Film:1", "to": "Species:4"} -{"gid": "Character:1-homeworld-Planet:1", "label": "homeworld", "from": "Character:1", "to": "Planet:1"} -{"gid": "Character:1-films-Film:1", "label": "films", "from": "Character:1", "to": "Film:1"} -{"gid": "Character:1-species-Species:1", "label": "species", "from": "Character:1", "to": "Species:1"} -{"gid": "Character:1-starships-Starship:12", "label": "starships", "from": "Character:1", "to": "Starship:12"} -{"gid": "Character:2-homeworld-Planet:1", "label": "homeworld", "from": "Character:2", "to": "Planet:1"} -{"gid": "Character:2-films-Film:1", "label": "films", "from": "Character:2", "to": "Film:1"} -{"gid": "Character:2-species-Species:2", "label": "species", "from": "Character:2", "to": "Species:2"} -{"gid": "Character:3-films-Film:1", "label": "films", "from": "Character:3", "to": "Film:1"} -{"gid": "Character:3-species-Species:2", "label": "species", "from": "Character:3", "to": "Species:2"} -{"gid": "Character:4-homeworld-Planet:1", "label": "homeworld", "from": "Character:4", "to": "Planet:1"} -{"gid": "Character:4-films-Film:1", "label": "films", "from": "Character:4", "to": "Film:1"} -{"gid": "Character:4-species-Species:1", "label": "species", "from": "Character:4", "to": "Species:1"} -{"gid": "Character:4-starships-Starship:13", "label": "starships", "from": "Character:4", "to": "Starship:13"} -{"gid": "Character:5-homeworld-Planet:2", "label": "homeworld", "from": "Character:5", "to": "Planet:2"} -{"gid": "Character:5-films-Film:1", "label": "films", "from": "Character:5", "to": "Film:1"} -{"gid": "Character:5-species-Species:1", "label": "species", "from": "Character:5", "to": "Species:1"} -{"gid": "Character:6-homeworld-Planet:1", "label": "homeworld", "from": "Character:6", "to": "Planet:1"} -{"gid": "Character:6-films-Film:1", "label": "films", "from": "Character:6", "to": "Film:1"} -{"gid": "Character:6-species-Species:1", "label": "species", "from": "Character:6", "to": "Species:1"} -{"gid": "Character:7-homeworld-Planet:1", "label": "homeworld", "from": "Character:7", "to": "Planet:1"} -{"gid": "Character:7-films-Film:1", "label": "films", "from": "Character:7", "to": "Film:1"} -{"gid": "Character:7-species-Species:1", "label": "species", "from": "Character:7", "to": "Species:1"} -{"gid": "Character:8-homeworld-Planet:1", "label": "homeworld", "from": "Character:8", "to": "Planet:1"} -{"gid": "Character:8-films-Film:1", "label": "films", "from": "Character:8", "to": "Film:1"} -{"gid": "Character:8-species-Species:2", "label": "species", "from": "Character:8", "to": "Species:2"} -{"gid": "Character:9-homeworld-Planet:1", "label": "homeworld", "from": "Character:9", "to": "Planet:1"} -{"gid": "Character:9-films-Film:1", "label": "films", "from": "Character:9", "to": "Film:1"} -{"gid": "Character:9-species-Species:1", "label": "species", "from": "Character:9", "to": "Species:1"} -{"gid": "Character:9-starships-Starship:12", "label": "starships", "from": "Character:9", "to": "Starship:12"} -{"gid": "Character:10-films-Film:1", "label": "films", "from": "Character:10", "to": "Film:1"} -{"gid": "Character:10-species-Species:1", "label": "species", "from": "Character:10", "to": "Species:1"} -{"gid": "Character:12-films-Film:1", "label": "films", "from": "Character:12", "to": "Film:1"} -{"gid": "Character:12-species-Species:1", "label": "species", "from": "Character:12", "to": "Species:1"} -{"gid": "Character:13-films-Film:1", "label": "films", "from": "Character:13", "to": "Film:1"} -{"gid": "Character:13-species-Species:3", "label": "species", "from": "Character:13", "to": "Species:3"} -{"gid": "Character:13-starships-Starship:10", "label": "starships", "from": "Character:13", "to": "Starship:10"} -{"gid": "Character:14-films-Film:1", "label": "films", "from": "Character:14", "to": "Film:1"} -{"gid": "Character:14-species-Species:1", "label": "species", "from": "Character:14", "to": "Species:1"} -{"gid": "Character:14-starships-Starship:10", "label": "starships", "from": "Character:14", "to": "Starship:10"} -{"gid": "Character:15-films-Film:1", "label": "films", "from": "Character:15", "to": "Film:1"} -{"gid": "Character:15-species-Species:4", "label": "species", "from": "Character:15", "to": "Species:4"} -{"gid": "Character:16-films-Film:1", "label": "films", "from": "Character:16", "to": "Film:1"} -{"gid": "Character:16-species-Species:5", "label": "species", "from": "Character:16", "to": "Species:5"} -{"gid": "Character:18-films-Film:1", "label": "films", "from": "Character:18", "to": "Film:1"} -{"gid": "Character:18-species-Species:1", "label": "species", "from": "Character:18", "to": "Species:1"} -{"gid": "Character:18-starships-Starship:12", "label": "starships", "from": "Character:18", "to": "Starship:12"} -{"gid": "Character:19-films-Film:1", "label": "films", "from": "Character:19", "to": "Film:1"} -{"gid": "Character:19-species-Species:1", "label": "species", "from": "Character:19", "to": "Species:1"} -{"gid": "Character:19-starships-Starship:12", "label": "starships", "from": "Character:19", "to": "Starship:12"} -{"gid": "Character:81-homeworld-Planet:2", "label": "homeworld", "from": "Character:81", "to": "Planet:2"} -{"gid": "Character:81-films-Film:1", "label": "films", "from": "Character:81", "to": "Film:1"} -{"gid": "Character:81-species-Species:1", "label": "species", "from": "Character:81", "to": "Species:1"} -{"gid": "Planet:2-residents-Character:5", "label": "residents", "from": "Planet:2", "to": "Character:5"} -{"gid": "Planet:2-residents-Character:81", "label": "residents", "from": "Planet:2", "to": "Character:81"} -{"gid": "Planet:2-films-Film:1", "label": "films", "from": "Planet:2", "to": "Film:1"} -{"gid": "Planet:3-films-Film:1", "label": "films", "from": "Planet:3", "to": "Film:1"} -{"gid": "Planet:1-residents-Character:1", "label": "residents", "from": "Planet:1", "to": "Character:1"} -{"gid": "Planet:1-residents-Character:2", "label": "residents", "from": "Planet:1", "to": "Character:2"} -{"gid": "Planet:1-residents-Character:4", "label": "residents", "from": "Planet:1", "to": "Character:4"} -{"gid": "Planet:1-residents-Character:6", "label": "residents", "from": "Planet:1", "to": "Character:6"} -{"gid": "Planet:1-residents-Character:7", "label": "residents", "from": "Planet:1", "to": "Character:7"} -{"gid": "Planet:1-residents-Character:8", "label": "residents", "from": "Planet:1", "to": "Character:8"} -{"gid": "Planet:1-residents-Character:9", "label": "residents", "from": "Planet:1", "to": "Character:9"} -{"gid": "Planet:1-films-Film:1", "label": "films", "from": "Planet:1", "to": "Film:1"} -{"gid": "Species:5-people-Character:16", "label": "people", "from": "Species:5", "to": "Character:16"} -{"gid": "Species:5-films-Film:1", "label": "films", "from": "Species:5", "to": "Film:1"} -{"gid": "Species:3-people-Character:13", "label": "people", "from": "Species:3", "to": "Character:13"} -{"gid": "Species:3-films-Film:1", "label": "films", "from": "Species:3", "to": "Film:1"} -{"gid": "Species:2-people-Character:2", "label": "people", "from": "Species:2", "to": "Character:2"} -{"gid": "Species:2-people-Character:3", "label": "people", "from": "Species:2", "to": "Character:3"} -{"gid": "Species:2-people-Character:8", "label": "people", "from": "Species:2", "to": "Character:8"} -{"gid": "Species:2-films-Film:1", "label": "films", "from": "Species:2", "to": "Film:1"} -{"gid": "Species:1-people-Character:1", "label": "people", "from": "Species:1", "to": "Character:1"} -{"gid": "Species:1-people-Character:4", "label": "people", "from": "Species:1", "to": "Character:4"} -{"gid": "Species:1-people-Character:5", "label": "people", "from": "Species:1", "to": "Character:5"} -{"gid": "Species:1-people-Character:6", "label": "people", "from": "Species:1", "to": "Character:6"} -{"gid": "Species:1-people-Character:7", "label": "people", "from": "Species:1", "to": "Character:7"} -{"gid": "Species:1-people-Character:9", "label": "people", "from": "Species:1", "to": "Character:9"} -{"gid": "Species:1-people-Character:10", "label": "people", "from": "Species:1", "to": "Character:10"} -{"gid": "Species:1-people-Character:12", "label": "people", "from": "Species:1", "to": "Character:12"} -{"gid": "Species:1-people-Character:14", "label": "people", "from": "Species:1", "to": "Character:14"} -{"gid": "Species:1-people-Character:18", "label": "people", "from": "Species:1", "to": "Character:18"} -{"gid": "Species:1-people-Character:19", "label": "people", "from": "Species:1", "to": "Character:19"} -{"gid": "Species:1-people-Character:81", "label": "people", "from": "Species:1", "to": "Character:81"} -{"gid": "Species:1-films-Film:1", "label": "films", "from": "Species:1", "to": "Film:1"} -{"gid": "Species:4-people-Character:15", "label": "people", "from": "Species:4", "to": "Character:15"} -{"gid": "Species:4-films-Film:1", "label": "films", "from": "Species:4", "to": "Film:1"} -{"gid": "Starship:5-films-Film:1", "label": "films", "from": "Starship:5", "to": "Film:1"} -{"gid": "Starship:9-films-Film:1", "label": "films", "from": "Starship:9", "to": "Film:1"} -{"gid": "Starship:10-pilots-Character:13", "label": "pilots", "from": "Starship:10", "to": "Character:13"} -{"gid": "Starship:10-pilots-Character:14", "label": "pilots", "from": "Starship:10", "to": "Character:14"} -{"gid": "Starship:10-films-Film:1", "label": "films", "from": "Starship:10", "to": "Film:1"} -{"gid": "Starship:11-films-Film:1", "label": "films", "from": "Starship:11", "to": "Film:1"} -{"gid": "Starship:12-pilots-Character:1", "label": "pilots", "from": "Starship:12", "to": "Character:1"} -{"gid": "Starship:12-pilots-Character:9", "label": "pilots", "from": "Starship:12", "to": "Character:9"} -{"gid": "Starship:12-pilots-Character:18", "label": "pilots", "from": "Starship:12", "to": "Character:18"} -{"gid": "Starship:12-pilots-Character:19", "label": "pilots", "from": "Starship:12", "to": "Character:19"} -{"gid": "Starship:12-films-Film:1", "label": "films", "from": "Starship:12", "to": "Film:1"} -{"gid": "Starship:13-pilots-Character:4", "label": "pilots", "from": "Starship:13", "to": "Character:4"} -{"gid": "Starship:13-films-Film:1", "label": "films", "from": "Starship:13", "to": "Film:1"} -{"gid": "Starship:3-films-Film:1", "label": "films", "from": "Starship:3", "to": "Film:1"} -{"gid": "Starship:2-films-Film:1", "label": "films", "from": "Starship:2", "to": "Film:1"} -{"gid": "Vehicle:4-films-Film:1", "label": "films", "from": "Vehicle:4", "to": "Film:1"} -{"gid": "Vehicle:6-films-Film:1", "label": "films", "from": "Vehicle:6", "to": "Film:1"} -{"gid": "Vehicle:7-films-Film:1", "label": "films", "from": "Vehicle:7", "to": "Film:1"} -{"gid": "Vehicle:8-films-Film:1", "label": "films", "from": "Vehicle:8", "to": "Film:1"} +{"_id": "Film:1-characters-Character:1", "_label": "characters", "_from": "Film:1", "_to": "Character:1"} +{"_id": "Film:1-characters-Character:2", "_label": "characters", "_from": "Film:1", "_to": "Character:2"} +{"_id": "Film:1-characters-Character:3", "_label": "characters", "_from": "Film:1", "_to": "Character:3"} +{"_id": "Film:1-characters-Character:4", "_label": "characters", "_from": "Film:1", "_to": "Character:4"} +{"_id": "Film:1-characters-Character:5", "_label": "characters", "_from": "Film:1", "_to": "Character:5"} +{"_id": "Film:1-characters-Character:6", "_label": "characters", "_from": "Film:1", "_to": "Character:6"} +{"_id": "Film:1-characters-Character:7", "_label": "characters", "_from": "Film:1", "_to": "Character:7"} +{"_id": "Film:1-characters-Character:8", "_label": "characters", "_from": "Film:1", "_to": "Character:8"} +{"_id": "Film:1-characters-Character:9", "_label": "characters", "_from": "Film:1", "_to": "Character:9"} +{"_id": "Film:1-characters-Character:10", "_label": "characters", "_from": "Film:1", "_to": "Character:10"} +{"_id": "Film:1-characters-Character:12", "_label": "characters", "_from": "Film:1", "_to": "Character:12"} +{"_id": "Film:1-characters-Character:13", "_label": "characters", "_from": "Film:1", "_to": "Character:13"} +{"_id": "Film:1-characters-Character:14", "_label": "characters", "_from": "Film:1", "_to": "Character:14"} +{"_id": "Film:1-characters-Character:15", "_label": "characters", "_from": "Film:1", "_to": "Character:15"} +{"_id": "Film:1-characters-Character:16", "_label": "characters", "_from": "Film:1", "_to": "Character:16"} +{"_id": "Film:1-characters-Character:18", "_label": "characters", "_from": "Film:1", "_to": "Character:18"} +{"_id": "Film:1-characters-Character:19", "_label": "characters", "_from": "Film:1", "_to": "Character:19"} +{"_id": "Film:1-characters-Character:81", "_label": "characters", "_from": "Film:1", "_to": "Character:81"} +{"_id": "Film:1-planets-Planet:2", "_label": "planets", "_from": "Film:1", "_to": "Planet:2", "scene_count" : 10} +{"_id": "Film:1-planets-Planet:3", "_label": "planets", "_from": "Film:1", "_to": "Planet:3", "scene_count" : 15} +{"_id": "Film:1-planets-Planet:1", "_label": "planets", "_from": "Film:1", "_to": "Planet:1", "scene_count" : 20} +{"_id": "Film:1-starships-Starship:2", "_label": "starships", "_from": "Film:1", "_to": "Starship:2"} +{"_id": "Film:1-starships-Starship:3", "_label": "starships", "_from": "Film:1", "_to": "Starship:3"} +{"_id": "Film:1-starships-Starship:5", "_label": "starships", "_from": "Film:1", "_to": "Starship:5"} +{"_id": "Film:1-starships-Starship:9", "_label": "starships", "_from": "Film:1", "_to": "Starship:9"} +{"_id": "Film:1-starships-Starship:10", "_label": "starships", "_from": "Film:1", "_to": "Starship:10"} +{"_id": "Film:1-starships-Starship:11", "_label": "starships", "_from": "Film:1", "_to": "Starship:11"} +{"_id": "Film:1-starships-Starship:12", "_label": "starships", "_from": "Film:1", "_to": "Starship:12"} +{"_id": "Film:1-starships-Starship:13", "_label": "starships", "_from": "Film:1", "_to": "Starship:13"} +{"_id": "Film:1-vehicles-Vehicle:4", "_label": "vehicles", "_from": "Film:1", "_to": "Vehicle:4"} +{"_id": "Film:1-vehicles-Vehicle:6", "_label": "vehicles", "_from": "Film:1", "_to": "Vehicle:6"} +{"_id": "Film:1-vehicles-Vehicle:7", "_label": "vehicles", "_from": "Film:1", "_to": "Vehicle:7"} +{"_id": "Film:1-vehicles-Vehicle:8", "_label": "vehicles", "_from": "Film:1", "_to": "Vehicle:8"} +{"_id": "Film:1-species-Species:5", "_label": "species", "_from": "Film:1", "_to": "Species:5"} +{"_id": "Film:1-species-Species:3", "_label": "species", "_from": "Film:1", "_to": "Species:3"} +{"_id": "Film:1-species-Species:2", "_label": "species", "_from": "Film:1", "_to": "Species:2"} +{"_id": "Film:1-species-Species:1", "_label": "species", "_from": "Film:1", "_to": "Species:1"} +{"_id": "Film:1-species-Species:4", "_label": "species", "_from": "Film:1", "_to": "Species:4"} +{"_id": "Character:1-homeworld-Planet:1", "_label": "homeworld", "_from": "Character:1", "_to": "Planet:1"} +{"_id": "Character:1-films-Film:1", "_label": "films", "_from": "Character:1", "_to": "Film:1"} +{"_id": "Character:1-species-Species:1", "_label": "species", "_from": "Character:1", "_to": "Species:1"} +{"_id": "Character:1-starships-Starship:12", "_label": "starships", "_from": "Character:1", "_to": "Starship:12"} +{"_id": "Character:2-homeworld-Planet:1", "_label": "homeworld", "_from": "Character:2", "_to": "Planet:1"} +{"_id": "Character:2-films-Film:1", "_label": "films", "_from": "Character:2", "_to": "Film:1"} +{"_id": "Character:2-species-Species:2", "_label": "species", "_from": "Character:2", "_to": "Species:2"} +{"_id": "Character:3-films-Film:1", "_label": "films", "_from": "Character:3", "_to": "Film:1"} +{"_id": "Character:3-species-Species:2", "_label": "species", "_from": "Character:3", "_to": "Species:2"} +{"_id": "Character:4-homeworld-Planet:1", "_label": "homeworld", "_from": "Character:4", "_to": "Planet:1"} +{"_id": "Character:4-films-Film:1", "_label": "films", "_from": "Character:4", "_to": "Film:1"} +{"_id": "Character:4-species-Species:1", "_label": "species", "_from": "Character:4", "_to": "Species:1"} +{"_id": "Character:4-starships-Starship:13", "_label": "starships", "_from": "Character:4", "_to": "Starship:13"} +{"_id": "Character:5-homeworld-Planet:2", "_label": "homeworld", "_from": "Character:5", "_to": "Planet:2"} +{"_id": "Character:5-films-Film:1", "_label": "films", "_from": "Character:5", "_to": "Film:1"} +{"_id": "Character:5-species-Species:1", "_label": "species", "_from": "Character:5", "_to": "Species:1"} +{"_id": "Character:6-homeworld-Planet:1", "_label": "homeworld", "_from": "Character:6", "_to": "Planet:1"} +{"_id": "Character:6-films-Film:1", "_label": "films", "_from": "Character:6", "_to": "Film:1"} +{"_id": "Character:6-species-Species:1", "_label": "species", "_from": "Character:6", "_to": "Species:1"} +{"_id": "Character:7-homeworld-Planet:1", "_label": "homeworld", "_from": "Character:7", "_to": "Planet:1"} +{"_id": "Character:7-films-Film:1", "_label": "films", "_from": "Character:7", "_to": "Film:1"} +{"_id": "Character:7-species-Species:1", "_label": "species", "_from": "Character:7", "_to": "Species:1"} +{"_id": "Character:8-homeworld-Planet:1", "_label": "homeworld", "_from": "Character:8", "_to": "Planet:1"} +{"_id": "Character:8-films-Film:1", "_label": "films", "_from": "Character:8", "_to": "Film:1"} +{"_id": "Character:8-species-Species:2", "_label": "species", "_from": "Character:8", "_to": "Species:2"} +{"_id": "Character:9-homeworld-Planet:1", "_label": "homeworld", "_from": "Character:9", "_to": "Planet:1"} +{"_id": "Character:9-films-Film:1", "_label": "films", "_from": "Character:9", "_to": "Film:1"} +{"_id": "Character:9-species-Species:1", "_label": "species", "_from": "Character:9", "_to": "Species:1"} +{"_id": "Character:9-starships-Starship:12", "_label": "starships", "_from": "Character:9", "_to": "Starship:12"} +{"_id": "Character:10-films-Film:1", "_label": "films", "_from": "Character:10", "_to": "Film:1"} +{"_id": "Character:10-species-Species:1", "_label": "species", "_from": "Character:10", "_to": "Species:1"} +{"_id": "Character:12-films-Film:1", "_label": "films", "_from": "Character:12", "_to": "Film:1"} +{"_id": "Character:12-species-Species:1", "_label": "species", "_from": "Character:12", "_to": "Species:1"} +{"_id": "Character:13-films-Film:1", "_label": "films", "_from": "Character:13", "_to": "Film:1"} +{"_id": "Character:13-species-Species:3", "_label": "species", "_from": "Character:13", "_to": "Species:3"} +{"_id": "Character:13-starships-Starship:10", "_label": "starships", "_from": "Character:13", "_to": "Starship:10"} +{"_id": "Character:14-films-Film:1", "_label": "films", "_from": "Character:14", "_to": "Film:1"} +{"_id": "Character:14-species-Species:1", "_label": "species", "_from": "Character:14", "_to": "Species:1"} +{"_id": "Character:14-starships-Starship:10", "_label": "starships", "_from": "Character:14", "_to": "Starship:10"} +{"_id": "Character:15-films-Film:1", "_label": "films", "_from": "Character:15", "_to": "Film:1"} +{"_id": "Character:15-species-Species:4", "_label": "species", "_from": "Character:15", "_to": "Species:4"} +{"_id": "Character:16-films-Film:1", "_label": "films", "_from": "Character:16", "_to": "Film:1"} +{"_id": "Character:16-species-Species:5", "_label": "species", "_from": "Character:16", "_to": "Species:5"} +{"_id": "Character:18-films-Film:1", "_label": "films", "_from": "Character:18", "_to": "Film:1"} +{"_id": "Character:18-species-Species:1", "_label": "species", "_from": "Character:18", "_to": "Species:1"} +{"_id": "Character:18-starships-Starship:12", "_label": "starships", "_from": "Character:18", "_to": "Starship:12"} +{"_id": "Character:19-films-Film:1", "_label": "films", "_from": "Character:19", "_to": "Film:1"} +{"_id": "Character:19-species-Species:1", "_label": "species", "_from": "Character:19", "_to": "Species:1"} +{"_id": "Character:19-starships-Starship:12", "_label": "starships", "_from": "Character:19", "_to": "Starship:12"} +{"_id": "Character:81-homeworld-Planet:2", "_label": "homeworld", "_from": "Character:81", "_to": "Planet:2"} +{"_id": "Character:81-films-Film:1", "_label": "films", "_from": "Character:81", "_to": "Film:1"} +{"_id": "Character:81-species-Species:1", "_label": "species", "_from": "Character:81", "_to": "Species:1"} +{"_id": "Planet:2-residents-Character:5", "_label": "residents", "_from": "Planet:2", "_to": "Character:5"} +{"_id": "Planet:2-residents-Character:81", "_label": "residents", "_from": "Planet:2", "_to": "Character:81"} +{"_id": "Planet:2-films-Film:1", "_label": "films", "_from": "Planet:2", "_to": "Film:1"} +{"_id": "Planet:3-films-Film:1", "_label": "films", "_from": "Planet:3", "_to": "Film:1"} +{"_id": "Planet:1-residents-Character:1", "_label": "residents", "_from": "Planet:1", "_to": "Character:1"} +{"_id": "Planet:1-residents-Character:2", "_label": "residents", "_from": "Planet:1", "_to": "Character:2"} +{"_id": "Planet:1-residents-Character:4", "_label": "residents", "_from": "Planet:1", "_to": "Character:4"} +{"_id": "Planet:1-residents-Character:6", "_label": "residents", "_from": "Planet:1", "_to": "Character:6"} +{"_id": "Planet:1-residents-Character:7", "_label": "residents", "_from": "Planet:1", "_to": "Character:7"} +{"_id": "Planet:1-residents-Character:8", "_label": "residents", "_from": "Planet:1", "_to": "Character:8"} +{"_id": "Planet:1-residents-Character:9", "_label": "residents", "_from": "Planet:1", "_to": "Character:9"} +{"_id": "Planet:1-films-Film:1", "_label": "films", "_from": "Planet:1", "_to": "Film:1"} +{"_id": "Species:5-people-Character:16", "_label": "people", "_from": "Species:5", "_to": "Character:16"} +{"_id": "Species:5-films-Film:1", "_label": "films", "_from": "Species:5", "_to": "Film:1"} +{"_id": "Species:3-people-Character:13", "_label": "people", "_from": "Species:3", "_to": "Character:13"} +{"_id": "Species:3-films-Film:1", "_label": "films", "_from": "Species:3", "_to": "Film:1"} +{"_id": "Species:2-people-Character:2", "_label": "people", "_from": "Species:2", "_to": "Character:2"} +{"_id": "Species:2-people-Character:3", "_label": "people", "_from": "Species:2", "_to": "Character:3"} +{"_id": "Species:2-people-Character:8", "_label": "people", "_from": "Species:2", "_to": "Character:8"} +{"_id": "Species:2-films-Film:1", "_label": "films", "_from": "Species:2", "_to": "Film:1"} +{"_id": "Species:1-people-Character:1", "_label": "people", "_from": "Species:1", "_to": "Character:1"} +{"_id": "Species:1-people-Character:4", "_label": "people", "_from": "Species:1", "_to": "Character:4"} +{"_id": "Species:1-people-Character:5", "_label": "people", "_from": "Species:1", "_to": "Character:5"} +{"_id": "Species:1-people-Character:6", "_label": "people", "_from": "Species:1", "_to": "Character:6"} +{"_id": "Species:1-people-Character:7", "_label": "people", "_from": "Species:1", "_to": "Character:7"} +{"_id": "Species:1-people-Character:9", "_label": "people", "_from": "Species:1", "_to": "Character:9"} +{"_id": "Species:1-people-Character:10", "_label": "people", "_from": "Species:1", "_to": "Character:10"} +{"_id": "Species:1-people-Character:12", "_label": "people", "_from": "Species:1", "_to": "Character:12"} +{"_id": "Species:1-people-Character:14", "_label": "people", "_from": "Species:1", "_to": "Character:14"} +{"_id": "Species:1-people-Character:18", "_label": "people", "_from": "Species:1", "_to": "Character:18"} +{"_id": "Species:1-people-Character:19", "_label": "people", "_from": "Species:1", "_to": "Character:19"} +{"_id": "Species:1-people-Character:81", "_label": "people", "_from": "Species:1", "_to": "Character:81"} +{"_id": "Species:1-films-Film:1", "_label": "films", "_from": "Species:1", "_to": "Film:1"} +{"_id": "Species:4-people-Character:15", "_label": "people", "_from": "Species:4", "_to": "Character:15"} +{"_id": "Species:4-films-Film:1", "_label": "films", "_from": "Species:4", "_to": "Film:1"} +{"_id": "Starship:5-films-Film:1", "_label": "films", "_from": "Starship:5", "_to": "Film:1"} +{"_id": "Starship:9-films-Film:1", "_label": "films", "_from": "Starship:9", "_to": "Film:1"} +{"_id": "Starship:10-pilots-Character:13", "_label": "pilots", "_from": "Starship:10", "_to": "Character:13"} +{"_id": "Starship:10-pilots-Character:14", "_label": "pilots", "_from": "Starship:10", "_to": "Character:14"} +{"_id": "Starship:10-films-Film:1", "_label": "films", "_from": "Starship:10", "_to": "Film:1"} +{"_id": "Starship:11-films-Film:1", "_label": "films", "_from": "Starship:11", "_to": "Film:1"} +{"_id": "Starship:12-pilots-Character:1", "_label": "pilots", "_from": "Starship:12", "_to": "Character:1"} +{"_id": "Starship:12-pilots-Character:9", "_label": "pilots", "_from": "Starship:12", "_to": "Character:9"} +{"_id": "Starship:12-pilots-Character:18", "_label": "pilots", "_from": "Starship:12", "_to": "Character:18"} +{"_id": "Starship:12-pilots-Character:19", "_label": "pilots", "_from": "Starship:12", "_to": "Character:19"} +{"_id": "Starship:12-films-Film:1", "_label": "films", "_from": "Starship:12", "_to": "Film:1"} +{"_id": "Starship:13-pilots-Character:4", "_label": "pilots", "_from": "Starship:13", "_to": "Character:4"} +{"_id": "Starship:13-films-Film:1", "_label": "films", "_from": "Starship:13", "_to": "Film:1"} +{"_id": "Starship:3-films-Film:1", "_label": "films", "_from": "Starship:3", "_to": "Film:1"} +{"_id": "Starship:2-films-Film:1", "_label": "films", "_from": "Starship:2", "_to": "Film:1"} +{"_id": "Vehicle:4-films-Film:1", "_label": "films", "_from": "Vehicle:4", "_to": "Film:1"} +{"_id": "Vehicle:6-films-Film:1", "_label": "films", "_from": "Vehicle:6", "_to": "Film:1"} +{"_id": "Vehicle:7-films-Film:1", "_label": "films", "_from": "Vehicle:7", "_to": "Film:1"} +{"_id": "Vehicle:8-films-Film:1", "_label": "films", "_from": "Vehicle:8", "_to": "Film:1"} diff --git a/conformance/graphs/swapi.vertices b/conformance/graphs/swapi.vertices index af3dff55a..eb8079dfb 100644 --- a/conformance/graphs/swapi.vertices +++ b/conformance/graphs/swapi.vertices @@ -1,39 +1,39 @@ -{"gid": "Character:1", "label": "Character", "data": {"system": {"created": "2014-12-09T13:50:51.644000Z", "edited": "2014-12-20T21:17:56.891000Z"}, "name": "Luke Skywalker", "height": 172, "mass": 77, "hair_color": "blond", "skin_color": "fair", "eye_color": "blue", "birth_year": "19BBY", "gender": "male", "url": "https://swapi.co/api/people/1/"}} -{"gid": "Character:2", "label": "Character", "data": {"system": {"created": "2014-12-10T15:10:51.357000Z", "edited": "2014-12-20T21:17:50.309000Z"}, "name": "C-3PO", "height": 167, "mass": 75, "hair_color": null, "skin_color": "gold", "eye_color": "yellow", "birth_year": "112BBY", "gender": null, "url": "https://swapi.co/api/people/2/"}} -{"gid": "Character:3", "label": "Character", "data": {"system": {"created": "2014-12-10T15:11:50.376000Z", "edited": "2014-12-20T21:17:50.311000Z"}, "name": "R2-D2", "height": 96, "mass": 32, "hair_color": null, "skin_color": "white, blue", "eye_color": "red", "birth_year": "33BBY", "gender": null, "url": "https://swapi.co/api/people/3/"}} -{"gid": "Character:4", "label": "Character", "data": {"system": {"created": "2014-12-10T15:18:20.704000Z", "edited": "2014-12-20T21:17:50.313000Z"}, "name": "Darth Vader", "height": 202, "mass": 136, "hair_color": "none", "skin_color": "white", "eye_color": "yellow", "birth_year": "41.9BBY", "gender": "male", "url": "https://swapi.co/api/people/4/"}} -{"gid": "Character:5", "label": "Character", "data": {"system": {"created": "2014-12-10T15:20:09.791000Z", "edited": "2014-12-20T21:17:50.315000Z"}, "name": "Leia Organa", "height": 150, "mass": 49, "hair_color": "brown", "skin_color": "light", "eye_color": "brown", "birth_year": "19BBY", "gender": "female", "url": "https://swapi.co/api/people/5/"}} -{"gid": "Character:6", "label": "Character", "data": {"system": {"created": "2014-12-10T15:52:14.024000Z", "edited": "2014-12-20T21:17:50.317000Z"}, "name": "Owen Lars", "height": 178, "mass": 120, "hair_color": "brown, grey", "skin_color": "light", "eye_color": "blue", "birth_year": "52BBY", "gender": "male", "url": "https://swapi.co/api/people/6/"}} -{"gid": "Character:7", "label": "Character", "data": {"system": {"created": "2014-12-10T15:53:41.121000Z", "edited": "2014-12-20T21:17:50.319000Z"}, "name": "Beru Whitesun lars", "height": 165, "mass": 75, "hair_color": "brown", "skin_color": "light", "eye_color": "blue", "birth_year": "47BBY", "gender": "female", "url": "https://swapi.co/api/people/7/"}} -{"gid": "Character:8", "label": "Character", "data": {"system": {"created": "2014-12-10T15:57:50.959000Z", "edited": "2014-12-20T21:17:50.321000Z"}, "name": "R5-D4", "height": 97, "mass": 32, "hair_color": null, "skin_color": "white, red", "eye_color": "red", "birth_year": "unknown", "gender": null, "url": "https://swapi.co/api/people/8/"}} -{"gid": "Character:9", "label": "Character", "data": {"system": {"created": "2014-12-10T15:59:50.509000Z", "edited": "2014-12-20T21:17:50.323000Z"}, "name": "Biggs Darklighter", "height": 183, "mass": 84, "hair_color": "black", "skin_color": "light", "eye_color": "brown", "birth_year": "24BBY", "gender": "male", "url": "https://swapi.co/api/people/9/"}} -{"gid": "Character:10", "label": "Character", "data": {"system": {"created": "2014-12-10T16:16:29.192000Z", "edited": "2014-12-20T21:17:50.325000Z"}, "name": "Obi-Wan Kenobi", "height": 182, "mass": 77, "hair_color": "auburn, white", "skin_color": "fair", "eye_color": "blue-gray", "birth_year": "57BBY", "gender": "male", "url": "https://swapi.co/api/people/10/"}} -{"gid": "Character:12", "label": "Character", "data": {"system": {"created": "2014-12-10T16:26:56.138000Z", "edited": "2014-12-20T21:17:50.330000Z"}, "name": "Wilhuff Tarkin", "height": 180, "mass": null, "hair_color": "auburn, grey", "skin_color": "fair", "eye_color": "blue", "birth_year": "64BBY", "gender": "male", "url": "https://swapi.co/api/people/12/"}} -{"gid": "Character:13", "label": "Character", "data": {"system": {"created": "2014-12-10T16:42:45.066000Z", "edited": "2014-12-20T21:17:50.332000Z"}, "name": "Chewbacca", "height": 228, "mass": 112, "hair_color": "brown", "skin_color": "unknown", "eye_color": "blue", "birth_year": "200BBY", "gender": "male", "url": "https://swapi.co/api/people/13/"}} -{"gid": "Character:14", "label": "Character", "data": {"system": {"created": "2014-12-10T16:49:14.582000Z", "edited": "2014-12-20T21:17:50.334000Z"}, "name": "Han Solo", "height": 180, "mass": 80, "hair_color": "brown", "skin_color": "fair", "eye_color": "brown", "birth_year": "29BBY", "gender": "male", "url": "https://swapi.co/api/people/14/"}} -{"gid": "Character:15", "label": "Character", "data": {"system": {"created": "2014-12-10T17:03:30.334000Z", "edited": "2014-12-20T21:17:50.336000Z"}, "name": "Greedo", "height": 173, "mass": 74, "hair_color": null, "skin_color": "green", "eye_color": "black", "birth_year": "44BBY", "gender": "male", "url": "https://swapi.co/api/people/15/"}} -{"gid": "Character:16", "label": "Character", "data": {"system": {"created": "2014-12-10T17:11:31.638000Z", "edited": "2014-12-20T21:17:50.338000Z"}, "name": "Jabba Desilijic Tiure", "height": 175, "mass": null, "hair_color": null, "skin_color": "green-tan, brown", "eye_color": "orange", "birth_year": "600BBY", "gender": "hermaphrodite", "url": "https://swapi.co/api/people/16/"}} -{"gid": "Character:18", "label": "Character", "data": {"system": {"created": "2014-12-12T11:08:06.469000Z", "edited": "2014-12-20T21:17:50.341000Z"}, "name": "Wedge Antilles", "height": 170, "mass": 77, "hair_color": "brown", "skin_color": "fair", "eye_color": "hazel", "birth_year": "21BBY", "gender": "male", "url": "https://swapi.co/api/people/18/"}} -{"gid": "Character:19", "label": "Character", "data": {"system": {"created": "2014-12-12T11:16:56.569000Z", "edited": "2014-12-20T21:17:50.343000Z"}, "name": "Jek Tono Porkins", "height": 180, "mass": 110, "hair_color": "brown", "skin_color": "fair", "eye_color": "blue", "birth_year": "unknown", "gender": "male", "url": "https://swapi.co/api/people/19/"}} -{"gid": "Character:81", "label": "Character", "data": {"system": {"created": "2014-12-20T19:49:35.583000Z", "edited": "2014-12-20T21:17:50.493000Z"}, "name": "Raymus Antilles", "height": 188, "mass": 79, "hair_color": "brown", "skin_color": "light", "eye_color": "brown", "birth_year": "unknown", "gender": "male", "url": "https://swapi.co/api/people/81/"}} -{"gid": "Planet:2", "label": "Planet", "data": {"system": {"created": "2014-12-10T11:35:48.479000Z", "edited": "2014-12-20T20:58:18.420000Z"}, "name": "Alderaan", "rotation_period": 24, "orbital_period": 364, "diameter": 12500, "climate": "temperate", "gravity": null, "terrain": ["grasslands", "mountains"], "surface_water": 40, "population": 2000000000, "url": "https://swapi.co/api/planets/2/"}} -{"gid": "Planet:3", "label": "Planet", "data": {"system": {"created": "2014-12-10T11:37:19.144000Z", "edited": "2014-12-20T20:58:18.421000Z"}, "name": "Yavin IV", "rotation_period": 24, "orbital_period": 4818, "diameter": 10200, "climate": "temperate, tropical", "gravity": null, "terrain": ["jungle", "rainforests"], "surface_water": 8, "population": 1000, "url": "https://swapi.co/api/planets/3/"}} -{"gid": "Planet:1", "label": "Planet", "data": {"system": {"created": "2014-12-09T13:50:49.641000Z", "edited": "2014-12-21T20:48:04.175778Z"}, "name": "Tatooine", "rotation_period": 23, "orbital_period": 304, "diameter": 10465, "climate": "arid", "gravity": null, "terrain": ["desert"], "surface_water": 1, "population": 200000, "url": "https://swapi.co/api/planets/1/"}} -{"gid": "Starship:2", "label": "Starship", "data": {"system": {"created": "2014-12-10T14:20:33.369000Z", "edited": "2014-12-22T17:35:45.408368Z"}, "name": "CR90 corvette", "model": "CR90 corvette", "manufacturer": "Corellian Engineering Corporation", "cost_in_credits": 3500000, "length": 150.0, "max_atmosphering_speed": 950, "crew": 165, "passengers": 600, "cargo_capacity": 3000000, "consumables": "1 year", "hyperdrive_rating": 2.0, "MGLT": "60", "starship_class": "corvette", "url": "https://swapi.co/api/starships/2/"}} -{"gid": "Starship:3", "label": "Starship", "data": {"system": {"created": "2014-12-10T15:08:19.848000Z", "edited": "2014-12-22T17:35:44.410941Z"}, "name": "Star Destroyer", "model": "Imperial I-class Star Destroyer", "manufacturer": "Kuat Drive Yards", "cost_in_credits": 150000000, "length": null, "max_atmosphering_speed": 975, "crew": 47060, "passengers": 0, "cargo_capacity": 36000000, "consumables": "2 years", "hyperdrive_rating": 2.0, "MGLT": "60", "starship_class": "Star Destroyer", "url": "https://swapi.co/api/starships/3/"}} -{"gid": "Starship:5", "label": "Starship", "data": {"system": {"created": "2014-12-10T15:48:00.586000Z", "edited": "2014-12-22T17:35:44.431407Z"}, "name": "Sentinel-class landing craft", "model": "Sentinel-class landing craft", "manufacturer": "Sienar Fleet Systems, Cyngus Spaceworks", "cost_in_credits": 240000, "length": 38.0, "max_atmosphering_speed": 1000, "crew": 5, "passengers": 75, "cargo_capacity": 180000, "consumables": "1 month", "hyperdrive_rating": 1.0, "MGLT": "70", "starship_class": "landing craft", "url": "https://swapi.co/api/starships/5/"}} -{"gid": "Starship:9", "label": "Starship", "data": {"system": {"created": "2014-12-10T16:36:50.509000Z", "edited": "2014-12-22T17:35:44.452589Z"}, "name": "Death Star", "model": "DS-1 Orbital Battle Station", "manufacturer": "Imperial Department of Military Research, Sienar Fleet Systems", "cost_in_credits": 1000000000000, "length": 120000.0, "max_atmosphering_speed": null, "crew": 342953, "passengers": 843342, "cargo_capacity": 1000000000000, "consumables": "3 years", "hyperdrive_rating": 4.0, "MGLT": "10", "starship_class": "Deep Space Mobile Battlestation", "url": "https://swapi.co/api/starships/9/"}} -{"gid": "Starship:10", "label": "Starship", "data": {"system": {"created": "2014-12-10T16:59:45.094000Z", "edited": "2014-12-22T17:35:44.464156Z"}, "name": "Millennium Falcon", "model": "YT-1300 light freighter", "manufacturer": "Corellian Engineering Corporation", "cost_in_credits": 100000, "length": 34.37, "max_atmosphering_speed": 1050, "crew": 4, "passengers": 6, "cargo_capacity": 100000, "consumables": "2 months", "hyperdrive_rating": 0.5, "MGLT": "75", "starship_class": "Light freighter", "url": "https://swapi.co/api/starships/10/"}} -{"gid": "Starship:11", "label": "Starship", "data": {"system": {"created": "2014-12-12T11:00:39.817000Z", "edited": "2014-12-22T17:35:44.479706Z"}, "name": "Y-wing", "model": "BTL Y-wing", "manufacturer": "Koensayr Manufacturing", "cost_in_credits": 134999, "length": 14.0, "max_atmosphering_speed": null, "crew": 2, "passengers": 0, "cargo_capacity": 110, "consumables": "1 week", "hyperdrive_rating": 1.0, "MGLT": "80", "starship_class": "assault starfighter", "url": "https://swapi.co/api/starships/11/"}} -{"gid": "Starship:12", "label": "Starship", "data": {"system": {"created": "2014-12-12T11:19:05.340000Z", "edited": "2014-12-22T17:35:44.491233Z"}, "name": "X-wing", "model": "T-65 X-wing", "manufacturer": "Incom Corporation", "cost_in_credits": 149999, "length": 12.5, "max_atmosphering_speed": 1050, "crew": 1, "passengers": 0, "cargo_capacity": 110, "consumables": "1 week", "hyperdrive_rating": 1.0, "MGLT": "100", "starship_class": "Starfighter", "url": "https://swapi.co/api/starships/12/"}} -{"gid": "Starship:13", "label": "Starship", "data": {"system": {"created": "2014-12-12T11:21:32.991000Z", "edited": "2014-12-22T17:35:44.549047Z"}, "name": "TIE Advanced x1", "model": "Twin Ion Engine Advanced x1", "manufacturer": "Sienar Fleet Systems", "cost_in_credits": null, "length": 9.2, "max_atmosphering_speed": 1200, "crew": 1, "passengers": 0, "cargo_capacity": 150, "consumables": "5 days", "hyperdrive_rating": 1.0, "MGLT": "105", "starship_class": "Starfighter", "url": "https://swapi.co/api/starships/13/"}} -{"gid": "Vehicle:4", "label": "Vehicle", "data": {"system": {"created": "2014-12-10T15:36:25.724000Z", "edited": "2014-12-22T18:21:15.523587Z"}, "name": "Sand Crawler", "model": "Digger Crawler", "manufacturer": "Corellia Mining Corporation", "cost_in_credits": 150000, "length": 36.8, "max_atmosphering_speed": 30, "crew": 46, "passengers": 30, "cargo_capacity": 50000, "consumables": "2 months", "vehicle_class": "wheeled", "url": "https://swapi.co/api/vehicles/4/"}} -{"gid": "Vehicle:6", "label": "Vehicle", "data": {"system": {"created": "2014-12-10T16:01:52.434000Z", "edited": "2014-12-22T18:21:15.552614Z"}, "name": "T-16 skyhopper", "model": "T-16 skyhopper", "manufacturer": "Incom Corporation", "cost_in_credits": 14500, "length": 10.4, "max_atmosphering_speed": 1200, "crew": 1, "passengers": 1, "cargo_capacity": 50, "consumables": "0", "vehicle_class": "repulsorcraft", "url": "https://swapi.co/api/vehicles/6/"}} -{"gid": "Vehicle:7", "label": "Vehicle", "data": {"system": {"created": "2014-12-10T16:13:52.586000Z", "edited": "2014-12-22T18:21:15.583700Z"}, "name": "X-34 landspeeder", "model": "X-34 landspeeder", "manufacturer": "SoroSuub Corporation", "cost_in_credits": 10550, "length": 3.4, "max_atmosphering_speed": 250, "crew": 1, "passengers": 1, "cargo_capacity": 5, "consumables": "unknown", "vehicle_class": "repulsorcraft", "url": "https://swapi.co/api/vehicles/7/"}} -{"gid": "Vehicle:8", "label": "Vehicle", "data": {"system": {"created": "2014-12-10T16:33:52.860000Z", "edited": "2014-12-22T18:21:15.606149Z"}, "name": "TIE/LN starfighter", "model": "Twin Ion Engine/Ln Starfighter", "manufacturer": "Sienar Fleet Systems", "cost_in_credits": null, "length": 6.4, "max_atmosphering_speed": 1200, "crew": 1, "passengers": 0, "cargo_capacity": 65, "consumables": "2 days", "vehicle_class": "starfighter", "url": "https://swapi.co/api/vehicles/8/"}} -{"gid": "Species:5", "label": "Species", "data": {"system": {"created": "2014-12-10T17:12:50.410000Z", "edited": "2014-12-20T21:36:42.146000Z"}, "name": "Hutt", "classification": "gastropod", "designation": "sentient", "average_height": "300", "skin_colors": ["green", "brown", "tan"], "hair_colors": [], "eye_colors": ["yellow", "red"], "average_lifespan": 1000, "language": "Huttese", "url": "https://swapi.co/api/species/5/"}} -{"gid": "Species:3", "label": "Species", "data": {"system": {"created": "2014-12-10T16:44:31.486000Z", "edited": "2015-01-30T21:23:03.074598Z"}, "name": "Wookiee", "classification": "mammal", "designation": "sentient", "average_height": "210", "skin_colors": ["gray"], "hair_colors": ["black", "brown"], "eye_colors": ["blue", "green", "yellow", "brown", "golden", "red"], "average_lifespan": 400, "language": "Shyriiwook", "url": "https://swapi.co/api/species/3/"}} -{"gid": "Species:2", "label": "Species", "data": {"system": {"created": "2014-12-10T15:16:16.259000Z", "edited": "2015-04-17T06:59:43.869528Z"}, "name": "Droid", "classification": "artificial", "designation": "sentient", "average_height": null, "skin_colors": [], "hair_colors": [], "eye_colors": [], "average_lifespan": null, "language": null, "url": "https://swapi.co/api/species/2/"}} -{"gid": "Species:1", "label": "Species", "data": {"system": {"created": "2014-12-10T13:52:11.567000Z", "edited": "2015-04-17T06:59:55.850671Z"}, "name": "Human", "classification": "mammal", "designation": "sentient", "average_height": "180", "skin_colors": ["caucasian", "black", "asian", "hispanic"], "hair_colors": ["blonde", "brown", "black", "red"], "eye_colors": ["brown", "blue", "green", "hazel", "grey", "amber"], "average_lifespan": 120, "language": "Galactic Basic", "url": "https://swapi.co/api/species/1/"}} -{"gid": "Species:4", "label": "Species", "data": {"system": {"created": "2014-12-10T17:05:26.471000Z", "edited": "2016-07-19T13:27:03.156498Z"}, "name": "Rodian", "classification": "sentient", "designation": "reptilian", "average_height": "170", "skin_colors": ["green", "blue"], "hair_colors": [], "eye_colors": ["black"], "average_lifespan": null, "language": "Galactic Basic", "url": "https://swapi.co/api/species/4/"}} -{"gid": "Film:1", "label": "Film", "data": {"system": {"created": "2014-12-10T14:23:31.880000Z", "edited": "2015-04-11T09:46:52.774897Z"}, "title": "A New Hope", "episode_id": 4, "opening_crawl": "It is a period of civil war.\r\nRebel spaceships, striking\r\nfrom a hidden base, have won\r\ntheir first victory against\r\nthe evil Galactic Empire.\r\n\r\nDuring the battle, Rebel\r\nspies managed to steal secret\r\nplans to the Empire's\r\nultimate weapon, the DEATH\r\nSTAR, an armored space\r\nstation with enough power\r\nto destroy an entire planet.\r\n\r\nPursued by the Empire's\r\nsinister agents, Princess\r\nLeia races home aboard her\r\nstarship, custodian of the\r\nstolen plans that can save her\r\npeople and restore\r\nfreedom to the galaxy....", "director": "George Lucas", "producer": ["Gary Kurtz", "Rick McCallum"], "release_date": "1977-05-25", "url": "https://swapi.co/api/films/1/"}} +{"_id": "Character:1", "_label": "Character", "system": {"created": "2014-12-09T13:50:51.644000Z", "edited": "2014-12-20T21:17:56.891000Z"}, "name": "Luke Skywalker", "height": 172, "mass": 77, "hair_color": "blond", "skin_color": "fair", "eye_color": "blue", "birth_year": "19BBY", "gender": "male", "url": "https://swapi.co/api/people/1/"} +{"_id": "Character:2", "_label": "Character", "system": {"created": "2014-12-10T15:10:51.357000Z", "edited": "2014-12-20T21:17:50.309000Z"}, "name": "C-3PO", "height": 167, "mass": 75, "hair_color": null, "skin_color": "gold", "eye_color": "yellow", "birth_year": "112BBY", "gender": null, "url": "https://swapi.co/api/people/2/"} +{"_id": "Character:3", "_label": "Character", "system": {"created": "2014-12-10T15:11:50.376000Z", "edited": "2014-12-20T21:17:50.311000Z"}, "name": "R2-D2", "height": 96, "mass": 32, "hair_color": null, "skin_color": "white, blue", "eye_color": "red", "birth_year": "33BBY", "gender": null, "url": "https://swapi.co/api/people/3/"} +{"_id": "Character:4", "_label": "Character", "system": {"created": "2014-12-10T15:18:20.704000Z", "edited": "2014-12-20T21:17:50.313000Z"}, "name": "Darth Vader", "height": 202, "mass": 136, "hair_color": "none", "skin_color": "white", "eye_color": "yellow", "birth_year": "41.9BBY", "gender": "male", "url": "https://swapi.co/api/people/4/"} +{"_id": "Character:5", "_label": "Character", "system": {"created": "2014-12-10T15:20:09.791000Z", "edited": "2014-12-20T21:17:50.315000Z"}, "name": "Leia Organa", "height": 150, "mass": 49, "hair_color": "brown", "skin_color": "light", "eye_color": "brown", "birth_year": "19BBY", "gender": "female", "url": "https://swapi.co/api/people/5/"} +{"_id": "Character:6", "_label": "Character", "system": {"created": "2014-12-10T15:52:14.024000Z", "edited": "2014-12-20T21:17:50.317000Z"}, "name": "Owen Lars", "height": 178, "mass": 120, "hair_color": "brown, grey", "skin_color": "light", "eye_color": "blue", "birth_year": "52BBY", "gender": "male", "url": "https://swapi.co/api/people/6/"} +{"_id": "Character:7", "_label": "Character", "system": {"created": "2014-12-10T15:53:41.121000Z", "edited": "2014-12-20T21:17:50.319000Z"}, "name": "Beru Whitesun lars", "height": 165, "mass": 75, "hair_color": "brown", "skin_color": "light", "eye_color": "blue", "birth_year": "47BBY", "gender": "female", "url": "https://swapi.co/api/people/7/"} +{"_id": "Character:8", "_label": "Character", "system": {"created": "2014-12-10T15:57:50.959000Z", "edited": "2014-12-20T21:17:50.321000Z"}, "name": "R5-D4", "height": 97, "mass": 32, "hair_color": null, "skin_color": "white, red", "eye_color": "red", "birth_year": "unknown", "gender": null, "url": "https://swapi.co/api/people/8/"} +{"_id": "Character:9", "_label": "Character", "system": {"created": "2014-12-10T15:59:50.509000Z", "edited": "2014-12-20T21:17:50.323000Z"}, "name": "Biggs Darklighter", "height": 183, "mass": 84, "hair_color": "black", "skin_color": "light", "eye_color": "brown", "birth_year": "24BBY", "gender": "male", "url": "https://swapi.co/api/people/9/"} +{"_id": "Character:10", "_label": "Character", "system": {"created": "2014-12-10T16:16:29.192000Z", "edited": "2014-12-20T21:17:50.325000Z"}, "name": "Obi-Wan Kenobi", "height": 182, "mass": 77, "hair_color": "auburn, white", "skin_color": "fair", "eye_color": "blue-gray", "birth_year": "57BBY", "gender": "male", "url": "https://swapi.co/api/people/10/"} +{"_id": "Character:12", "_label": "Character", "system": {"created": "2014-12-10T16:26:56.138000Z", "edited": "2014-12-20T21:17:50.330000Z"}, "name": "Wilhuff Tarkin", "height": 180, "mass": null, "hair_color": "auburn, grey", "skin_color": "fair", "eye_color": "blue", "birth_year": "64BBY", "gender": "male", "url": "https://swapi.co/api/people/12/"} +{"_id": "Character:13", "_label": "Character", "system": {"created": "2014-12-10T16:42:45.066000Z", "edited": "2014-12-20T21:17:50.332000Z"}, "name": "Chewbacca", "height": 228, "mass": 112, "hair_color": "brown", "skin_color": "unknown", "eye_color": "blue", "birth_year": "200BBY", "gender": "male", "url": "https://swapi.co/api/people/13/"} +{"_id": "Character:14", "_label": "Character", "system": {"created": "2014-12-10T16:49:14.582000Z", "edited": "2014-12-20T21:17:50.334000Z"}, "name": "Han Solo", "height": 180, "mass": 80, "hair_color": "brown", "skin_color": "fair", "eye_color": "brown", "birth_year": "29BBY", "gender": "male", "url": "https://swapi.co/api/people/14/"} +{"_id": "Character:15", "_label": "Character", "system": {"created": "2014-12-10T17:03:30.334000Z", "edited": "2014-12-20T21:17:50.336000Z"}, "name": "Greedo", "height": 173, "mass": 74, "hair_color": null, "skin_color": "green", "eye_color": "black", "birth_year": "44BBY", "gender": "male", "url": "https://swapi.co/api/people/15/"} +{"_id": "Character:16", "_label": "Character", "system": {"created": "2014-12-10T17:11:31.638000Z", "edited": "2014-12-20T21:17:50.338000Z"}, "name": "Jabba Desilijic Tiure", "height": 175, "mass": null, "hair_color": null, "skin_color": "green-tan, brown", "eye_color": "orange", "birth_year": "600BBY", "gender": "hermaphrodite", "url": "https://swapi.co/api/people/16/"} +{"_id": "Character:18", "_label": "Character", "system": {"created": "2014-12-12T11:08:06.469000Z", "edited": "2014-12-20T21:17:50.341000Z"}, "name": "Wedge Antilles", "height": 170, "mass": 77, "hair_color": "brown", "skin_color": "fair", "eye_color": "hazel", "birth_year": "21BBY", "gender": "male", "url": "https://swapi.co/api/people/18/"} +{"_id": "Character:19", "_label": "Character", "system": {"created": "2014-12-12T11:16:56.569000Z", "edited": "2014-12-20T21:17:50.343000Z"}, "name": "Jek Tono Porkins", "height": 180, "mass": 110, "hair_color": "brown", "skin_color": "fair", "eye_color": "blue", "birth_year": "unknown", "gender": "male", "url": "https://swapi.co/api/people/19/"} +{"_id": "Character:81", "_label": "Character", "system": {"created": "2014-12-20T19:49:35.583000Z", "edited": "2014-12-20T21:17:50.493000Z"}, "name": "Raymus Antilles", "height": 188, "mass": 79, "hair_color": "brown", "skin_color": "light", "eye_color": "brown", "birth_year": "unknown", "gender": "male", "url": "https://swapi.co/api/people/81/"} +{"_id": "Planet:2", "_label": "Planet", "system": {"created": "2014-12-10T11:35:48.479000Z", "edited": "2014-12-20T20:58:18.420000Z"}, "name": "Alderaan", "rotation_period": 24, "orbital_period": 364, "diameter": 12500, "climate": "temperate", "gravity": null, "terrain": ["grasslands", "mountains"], "surface_water": 40, "population": 2000000000, "url": "https://swapi.co/api/planets/2/"} +{"_id": "Planet:3", "_label": "Planet", "system": {"created": "2014-12-10T11:37:19.144000Z", "edited": "2014-12-20T20:58:18.421000Z"}, "name": "Yavin IV", "rotation_period": 24, "orbital_period": 4818, "diameter": 10200, "climate": "temperate, tropical", "gravity": null, "terrain": ["jungle", "rainforests"], "surface_water": 8, "population": 1000, "url": "https://swapi.co/api/planets/3/"} +{"_id": "Planet:1", "_label": "Planet", "system": {"created": "2014-12-09T13:50:49.641000Z", "edited": "2014-12-21T20:48:04.175778Z"}, "name": "Tatooine", "rotation_period": 23, "orbital_period": 304, "diameter": 10465, "climate": "ar_id", "gravity": null, "terrain": ["desert"], "surface_water": 1, "population": 200000, "url": "https://swapi.co/api/planets/1/"} +{"_id": "Starship:2", "_label": "Starship", "system": {"created": "2014-12-10T14:20:33.369000Z", "edited": "2014-12-22T17:35:45.408368Z"}, "name": "CR90 corvette", "model": "CR90 corvette", "manufacturer": "Corellian Engineering Corporation", "cost_in_credits": 3500000, "length": 150.0, "max_atmosphering_speed": 950, "crew": 165, "passengers": 600, "cargo_capacity": 3000000, "consumables": "1 year", "hyperdrive_rating": 2.0, "MGLT": "60", "starship_class": "corvette", "url": "https://swapi.co/api/starships/2/"} +{"_id": "Starship:3", "_label": "Starship", "system": {"created": "2014-12-10T15:08:19.848000Z", "edited": "2014-12-22T17:35:44.410941Z"}, "name": "Star Destroyer", "model": "Imperial I-class Star Destroyer", "manufacturer": "Kuat Drive Yards", "cost_in_credits": 150000000, "length": null, "max_atmosphering_speed": 975, "crew": 47060, "passengers": 0, "cargo_capacity": 36000000, "consumables": "2 years", "hyperdrive_rating": 2.0, "MGLT": "60", "starship_class": "Star Destroyer", "url": "https://swapi.co/api/starships/3/"} +{"_id": "Starship:5", "_label": "Starship", "system": {"created": "2014-12-10T15:48:00.586000Z", "edited": "2014-12-22T17:35:44.431407Z"}, "name": "Sentinel-class landing craft", "model": "Sentinel-class landing craft", "manufacturer": "Sienar Fleet Systems, Cyngus Spaceworks", "cost_in_credits": 240000, "length": 38.0, "max_atmosphering_speed": 1000, "crew": 5, "passengers": 75, "cargo_capacity": 180000, "consumables": "1 month", "hyperdrive_rating": 1.0, "MGLT": "70", "starship_class": "landing craft", "url": "https://swapi.co/api/starships/5/"} +{"_id": "Starship:9", "_label": "Starship", "system": {"created": "2014-12-10T16:36:50.509000Z", "edited": "2014-12-22T17:35:44.452589Z"}, "name": "Death Star", "model": "DS-1 Orbital Battle Station", "manufacturer": "Imperial Department of Military Research, Sienar Fleet Systems", "cost_in_credits": 1000000000000, "length": 120000.0, "max_atmosphering_speed": null, "crew": 342953, "passengers": 843342, "cargo_capacity": 1000000000000, "consumables": "3 years", "hyperdrive_rating": 4.0, "MGLT": "10", "starship_class": "Deep Space Mobile Battlestation", "url": "https://swapi.co/api/starships/9/"} +{"_id": "Starship:10", "_label": "Starship", "system": {"created": "2014-12-10T16:59:45.094000Z", "edited": "2014-12-22T17:35:44.464156Z"}, "name": "Millennium Falcon", "model": "YT-1300 light freighter", "manufacturer": "Corellian Engineering Corporation", "cost_in_credits": 100000, "length": 34.37, "max_atmosphering_speed": 1050, "crew": 4, "passengers": 6, "cargo_capacity": 100000, "consumables": "2 months", "hyperdrive_rating": 0.5, "MGLT": "75", "starship_class": "Light freighter", "url": "https://swapi.co/api/starships/10/"} +{"_id": "Starship:11", "_label": "Starship", "system": {"created": "2014-12-12T11:00:39.817000Z", "edited": "2014-12-22T17:35:44.479706Z"}, "name": "Y-wing", "model": "BTL Y-wing", "manufacturer": "Koensayr Manufacturing", "cost_in_credits": 134999, "length": 14.0, "max_atmosphering_speed": null, "crew": 2, "passengers": 0, "cargo_capacity": 110, "consumables": "1 week", "hyperdrive_rating": 1.0, "MGLT": "80", "starship_class": "assault starfighter", "url": "https://swapi.co/api/starships/11/"} +{"_id": "Starship:12", "_label": "Starship", "system": {"created": "2014-12-12T11:19:05.340000Z", "edited": "2014-12-22T17:35:44.491233Z"}, "name": "X-wing", "model": "T-65 X-wing", "manufacturer": "Incom Corporation", "cost_in_credits": 149999, "length": 12.5, "max_atmosphering_speed": 1050, "crew": 1, "passengers": 0, "cargo_capacity": 110, "consumables": "1 week", "hyperdrive_rating": 1.0, "MGLT": "100", "starship_class": "Starfighter", "url": "https://swapi.co/api/starships/12/"} +{"_id": "Starship:13", "_label": "Starship", "system": {"created": "2014-12-12T11:21:32.991000Z", "edited": "2014-12-22T17:35:44.549047Z"}, "name": "TIE Advanced x1", "model": "Twin Ion Engine Advanced x1", "manufacturer": "Sienar Fleet Systems", "cost_in_credits": null, "length": 9.2, "max_atmosphering_speed": 1200, "crew": 1, "passengers": 0, "cargo_capacity": 150, "consumables": "5 days", "hyperdrive_rating": 1.0, "MGLT": "105", "starship_class": "Starfighter", "url": "https://swapi.co/api/starships/13/"} +{"_id": "Vehicle:4", "_label": "Vehicle", "system": {"created": "2014-12-10T15:36:25.724000Z", "edited": "2014-12-22T18:21:15.523587Z"}, "name": "Sand Crawler", "model": "Digger Crawler", "manufacturer": "Corellia Mining Corporation", "cost_in_credits": 150000, "length": 36.8, "max_atmosphering_speed": 30, "crew": 46, "passengers": 30, "cargo_capacity": 50000, "consumables": "2 months", "vehicle_class": "wheeled", "url": "https://swapi.co/api/vehicles/4/"} +{"_id": "Vehicle:6", "_label": "Vehicle", "system": {"created": "2014-12-10T16:01:52.434000Z", "edited": "2014-12-22T18:21:15.552614Z"}, "name": "T-16 skyhopper", "model": "T-16 skyhopper", "manufacturer": "Incom Corporation", "cost_in_credits": 14500, "length": 10.4, "max_atmosphering_speed": 1200, "crew": 1, "passengers": 1, "cargo_capacity": 50, "consumables": "0", "vehicle_class": "repulsorcraft", "url": "https://swapi.co/api/vehicles/6/"} +{"_id": "Vehicle:7", "_label": "Vehicle", "system": {"created": "2014-12-10T16:13:52.586000Z", "edited": "2014-12-22T18:21:15.583700Z"}, "name": "X-34 landspeeder", "model": "X-34 landspeeder", "manufacturer": "SoroSuub Corporation", "cost_in_credits": 10550, "length": 3.4, "max_atmosphering_speed": 250, "crew": 1, "passengers": 1, "cargo_capacity": 5, "consumables": "unknown", "vehicle_class": "repulsorcraft", "url": "https://swapi.co/api/vehicles/7/"} +{"_id": "Vehicle:8", "_label": "Vehicle", "system": {"created": "2014-12-10T16:33:52.860000Z", "edited": "2014-12-22T18:21:15.606149Z"}, "name": "TIE/LN starfighter", "model": "Twin Ion Engine/Ln Starfighter", "manufacturer": "Sienar Fleet Systems", "cost_in_credits": null, "length": 6.4, "max_atmosphering_speed": 1200, "crew": 1, "passengers": 0, "cargo_capacity": 65, "consumables": "2 days", "vehicle_class": "starfighter", "url": "https://swapi.co/api/vehicles/8/"} +{"_id": "Species:5", "_label": "Species", "system": {"created": "2014-12-10T17:12:50.410000Z", "edited": "2014-12-20T21:36:42.146000Z"}, "name": "Hutt", "classification": "gastropod", "designation": "sentient", "average_height": "300", "skin_colors": ["green", "brown", "tan"], "hair_colors": [], "eye_colors": ["yellow", "red"], "average_lifespan": 1000, "language": "Huttese", "url": "https://swapi.co/api/species/5/"} +{"_id": "Species:3", "_label": "Species", "system": {"created": "2014-12-10T16:44:31.486000Z", "edited": "2015-01-30T21:23:03.074598Z"}, "name": "Wookiee", "classification": "mammal", "designation": "sentient", "average_height": "210", "skin_colors": ["gray"], "hair_colors": ["black", "brown"], "eye_colors": ["blue", "green", "yellow", "brown", "golden", "red"], "average_lifespan": 400, "language": "Shyriiwook", "url": "https://swapi.co/api/species/3/"} +{"_id": "Species:2", "_label": "Species", "system": {"created": "2014-12-10T15:16:16.259000Z", "edited": "2015-04-17T06:59:43.869528Z"}, "name": "Droid", "classification": "artificial", "designation": "sentient", "average_height": null, "skin_colors": [], "hair_colors": [], "eye_colors": [], "average_lifespan": null, "language": null, "url": "https://swapi.co/api/species/2/"} +{"_id": "Species:1", "_label": "Species", "system": {"created": "2014-12-10T13:52:11.567000Z", "edited": "2015-04-17T06:59:55.850671Z"}, "name": "Human", "classification": "mammal", "designation": "sentient", "average_height": "180", "skin_colors": ["caucasian", "black", "asian", "hispanic"], "hair_colors": ["blonde", "brown", "black", "red"], "eye_colors": ["brown", "blue", "green", "hazel", "grey", "amber"], "average_lifespan": 120, "language": "Galactic Basic", "url": "https://swapi.co/api/species/1/"} +{"_id": "Species:4", "_label": "Species", "system": {"created": "2014-12-10T17:05:26.471000Z", "edited": "2016-07-19T13:27:03.156498Z"}, "name": "Rodian", "classification": "sentient", "designation": "reptilian", "average_height": "170", "skin_colors": ["green", "blue"], "hair_colors": [], "eye_colors": ["black"], "average_lifespan": null, "language": "Galactic Basic", "url": "https://swapi.co/api/species/4/"} +{"_id": "Film:1", "_label": "Film", "system": {"created": "2014-12-10T14:23:31.880000Z", "edited": "2015-04-11T09:46:52.774897Z"}, "title": "A New Hope", "episode__id": 4, "opening_crawl": "It is a period of civil war.\r\nRebel spaceships, striking\r\nfrom a h_idden base, have won\r\ntheir first victory against\r\nthe evil Galactic Empire.\r\n\r\nDuring the battle, Rebel\r\nspies managed to steal secret\r\nplans to the Empire's\r\nultimate weapon, the DEATH\r\nSTAR, an armored space\r\nstation with enough power\r\nto destroy an entire planet.\r\n\r\nPursued by the Empire's\r\nsinister agents, Princess\r\nLeia races home aboard her\r\nstarship, custodian of the\r\nstolen plans that can save her\r\npeople and restore\r\nfreedom to the galaxy....", "director": "George Lucas", "producer": ["Gary Kurtz", "Rick McCallum"], "release_date": "1977-05-25", "url": "https://swapi.co/api/films/1/"} \ No newline at end of file diff --git a/conformance/resources/swapi_create_subgraph.py b/conformance/resources/swapi_create_subgraph.py index 2421e839c..8698cba1d 100644 --- a/conformance/resources/swapi_create_subgraph.py +++ b/conformance/resources/swapi_create_subgraph.py @@ -8,26 +8,26 @@ with open("./swapi_vertices.json") as fh: for line in fh: line = json.loads(line) - verts[line["gid"]] = line + verts[line["_id"]] = line edges = {} with open("./swapi_edges.json") as fh: for line in fh: line = json.loads(line) - edges[line["gid"]] = line + edges[line["_id"]] = line G1 = nx.DiGraph() -for gid, e in edges.items(): - G1.add_edge(e["from"], e["to"]) +for _, e in edges.items(): + G1.add_edge(e["_from"], e["_to"]) G = nx.DiGraph() whitelist = list(G1.neighbors("Film:1")) + ["Film:1"] edges_sub = [] verts_sub = [verts[x] for x in whitelist] -for gid, e in edges.items(): - if e["from"] not in whitelist or e["to"] not in whitelist: +for _, e in edges.items(): + if e["_from"] not in whitelist or e["_to"] not in whitelist: continue - G.add_edge(e["from"], e["to"]) + G.add_edge(e["_from"], e["_to"]) edges_sub.append(e) # write subgraph to output files diff --git a/conformance/resources/swapi_etl.py b/conformance/resources/swapi_etl.py index e0415f933..e8f4a40b4 100644 --- a/conformance/resources/swapi_etl.py +++ b/conformance/resources/swapi_etl.py @@ -42,8 +42,8 @@ def create_vertex(label, data): - id = data["url"].replace("https://swapi.co/api/", "").strip("/").split("/")[1] - gid = "%s:%s" % (label, id) + loadid = data["url"].replace("https://swapi.co/api/", "").strip("/").split("/")[1] + id = "%s:%s" % (label, loadid) tdata = {"system": {}} for k, v in data.items(): if v == "n/a": @@ -69,7 +69,7 @@ def create_vertex(label, data): continue else: tdata[k] = v - return {"gid": gid, "label": label, "data": tdata} + return {"_id": id, "_label": label}.update(tdata) def create_edge(label, fid, tid): @@ -77,8 +77,8 @@ def create_edge(label, fid, tid): tlab, tid = tid.replace("https://swapi.co/api/", "").strip("/").split("/") fid = "%s:%s" % (edge_lookup[flab], fid) tid = "%s:%s" % (edge_lookup[tlab], tid) - return {"gid": "(%s)-[%s]->(%s)" % (fid, label, tid), - "label": label, "from": fid, "to": tid} + return {"_id": "(%s)-[%s]->(%s)" % (fid, label, tid), + "_label": label, "_from": fid, "_to": tid} def create_all_edges(doc): diff --git a/conformance/run_conformance.py b/conformance/run_conformance.py index f388cf5eb..075d3ef0e 100755 --- a/conformance/run_conformance.py +++ b/conformance/run_conformance.py @@ -8,7 +8,7 @@ print("Running Conformance with %s" % gripql.__file__) args = create_arg_parser() - + # returns test modules starting with "ot_" tests = filter_tests(args, prefix="ot_") diff --git a/conformance/run_kafka.py b/conformance/run_kafka.py new file mode 100644 index 000000000..38bf20aa0 --- /dev/null +++ b/conformance/run_kafka.py @@ -0,0 +1,301 @@ +from __future__ import absolute_import, print_function, unicode_literals + +import json +import os +import sys +import string +import random +from run_util import gripql, create_connection, Manager +import re +from kafka import KafkaConsumer +import logging +import time +import requests +from kafka import TopicPartition + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Define BASE directory +BASE = os.path.dirname(os.path.abspath(__file__)) +TOPIC = "gripHistory" +KAFKA_HOST = "localhost:9092" +GRIP_SERVER_URL = "http://localhost:8201" +USERNAME = "admin" +PASSWORD = "adminpassword" +TIMEOUTMS = 10000 # Integer for consistency + + +class KafkaManager(Manager): + """Common test methods.""" + def __init__(self, conn, readOnly=False, server=None, grip_config_file_path=None): + super().__init__(conn, readOnly, server, grip_config_file_path) + self.loadgraphname = "swapi" + self.kafka_consumer = self.init_kafka_consumer() + + def init_kafka_consumer(self): + """Initialize Kafka consumer for TOPIC.""" + try: + consumer = KafkaConsumer( + TOPIC, + bootstrap_servers=[KAFKA_HOST], + security_protocol='SASL_PLAINTEXT', + sasl_mechanism='PLAIN', + sasl_plain_username=USERNAME, + sasl_plain_password=PASSWORD, + auto_offset_reset='earliest', + enable_auto_commit=True, + value_deserializer=lambda x: self.deserialize_message(x), + session_timeout_ms=TIMEOUTMS + ) + logger.info("Connected to Kafka consumer") + return consumer + except Exception as e: + logger.error(f"Failed to connect to Kafka consumer: {e}") + return None + + def deserialize_message(self, data): + """Safely deserialize Kafka message.""" + if not data: + logger.debug("Received empty Kafka message") + return "" + try: + decoded = data.decode('utf-8') + return json.loads(decoded) + except json.JSONDecodeError: + return decoded + except UnicodeDecodeError: + logger.warning(f"Failed to decode message: {data!r}") + return None + + def test_bulk_load_test_graph(self): + """Test loading data into TEST graph and print Kafka messages.""" + errors, edges, vertices = [], [], [] + self.curGraph = "TEST" + self.id_generator() + logger.info(f"Creating graph: {self.curGraph}") + self._conn.addGraph(self.curGraph) + logger.info("CALLING ADD GRAPH +++++++++++++++++++++++++++++++++++++++") + + G = self._conn.graph(self.curGraph) + vertex_file = os.path.join(BASE, "graphs", f"{self.loadgraphname}.vertices") + logger.info(f"Loading vertices from: {vertex_file}") + if os.path.exists(vertex_file): + with open(vertex_file) as handle: + bulk = G.bulkAdd() + for line in handle: + data = json.loads(line.strip()) + id = data.get("_id", None) + if id is not None: + vertices.append(id) + bulk.addVertex(id=id, label=data["_label"], data=self.collect_fields_dict(data)) + _ = bulk.execute() + else: + raise FileNotFoundError(f"Vertex file not found: {vertex_file}") + + edge_file = os.path.join(BASE, "graphs", f"{self.loadgraphname}.edges") + logger.info(f"Loading edges from: {edge_file}") + if os.path.exists(edge_file): + with open(edge_file) as handle: + bulk = G.bulkAdd() + for line in handle: + data = json.loads(line.strip()) + id = data.get("_id", None) + if id is not None: + edges.append(id) + G.addEdge( + src=data["_from"], + dst=data["_to"], + id=data.get("_id", None), + label=data["_label"], + data=self.collect_fields_dict(data)) + _ = bulk.execute() + else: + raise FileNotFoundError(f"Edge file not found: {edge_file}") + + # Verify graph data + vertex_count_result = list(G.V().count()) + if not vertex_count_result or 'count' not in vertex_count_result[0]: + raise ValueError("Invalid vertex count response") + vertex_count = vertex_count_result[0]['count'] + assert vertex_count > 0, f"No vertices loaded into {self.curGraph}" + edge_count_result = list(G.V().outE().count()) + edge_count = edge_count_result[0]['count'] if edge_count_result else 0 + logger.info(f"Loaded {vertex_count} vertices and {edge_count} edges") + logger.info("CALLING DELETE GRAPH ------------------------------------------") + self._conn.deleteGraph(self.curGraph) + + return errors, edges, vertices + def test_write_from_kafka(self, toggle_delete_method, toggle_post_method, orig_vertex_counts, orig_edge_counts): + bulk_re = re.compile(r"^/v1/graph$") + graph_re = re.compile(r"^/v1/graph/([^/]+)$") + delete_vertex_re = re.compile(r"^/v1/graph/([^/]+)/vertex/([^/]+)$") + delete_edge_re = re.compile(r"^/v1/graph/([^/]+)/edge/([^/]+)$") + add_vertex_re = re.compile(r"^/v1/graph/([^/]+)/vertex$") + add_edge_re = re.compile(r"^/v1/graph/([^/]+)/edge$") + + created_graphs = set() # Track created graphs to avoid duplicates + messages = [] # Collect snapshot of messages + + logger.info(f"Capturing snapshot of Kafka messages from {TOPIC} topic...") + + # Get the current end offsets for all partitions of the topic + partitions = self.kafka_consumer.partitions_for_topic(TOPIC) + if not partitions: + logger.warning(f"No partitions found for topic {TOPIC}") + return + topic_partitions = [TopicPartition(TOPIC, p) for p in partitions] + end_offsets = self.kafka_consumer.end_offsets(topic_partitions) + + # Seek to the beginning of all assigned partitions + for tp in topic_partitions: + self.kafka_consumer.seek(tp, 0) # Seek to offset 0 (beginning) + + messages_read = {tp: 0 for tp in topic_partitions} + + # Poll for messages until we reach the initial end offsets + start_time = time.time() + while True: + message_batch = self.kafka_consumer.poll(timeout_ms=100) # Short timeout + if not message_batch: + if time.time() - start_time > TIMEOUTMS / 1000: + logger.info("Timeout reached while collecting initial messages") + break + continue + + for tp, records in message_batch.items(): + for msg in records: + if msg.value is not None: + messages.append(msg) + messages_read[tp] += 1 + + # Check if we've consumed up to the initial end offset for all partitions + all_caught_up = True + for tp in topic_partitions: + if messages_read[tp] < end_offsets[tp]: + all_caught_up = False + break + + if all_caught_up and messages: + break # All initial messages consumed + + logger.info(f"Collected {len(messages)} initial Kafka messages in snapshot") + + # Unsubscribe to stop further consumption + self.kafka_consumer.unsubscribe() + + # Process the captured messages + for msg in messages: + headers = dict(msg.headers) + path = headers.get('PATH', b'').decode() + method = headers.get('METHOD', b'').decode() + if not path or not method: + continue + + logger.info(f"Processing Kafka message: PATH={path}, METHOD={method}, VALUE={msg.value}") + + if method == "DELETE" and toggle_delete_method: + parts = path.split('/') + if bulk_re.match(path): + deleteBulk = msg.value + self.curGraph = deleteBulk["graph"] + logger.info(f"Deleting bulk graph: {self.curGraph}") + self._conn.graph(deleteBulk["graph"]).delete(edges=deleteBulk["edges"], vertices=deleteBulk["vertices"]) + elif delete_vertex_re.match(path): + graph_name = parts[-3] + vertex_id = parts[-1] + logger.info(f"Deleting vertex {vertex_id} from graph {graph_name}") + self._conn.graph(graph_name).deleteVertex(vertex_id) + elif delete_edge_re.match(path): + graph_name = parts[-3] + edge_id = parts[-1] + logger.info(f"Deleting edge {edge_id} from graph {graph_name}") + self._conn.graph(graph_name).deleteEdge(edge_id) + elif graph_re.match(path): + graph_name = path.split('/')[-1] + logger.info(f"CALLING DELETE GRAPH ------------------------------------------ for {graph_name}") + self._conn.deleteGraph(graph_name) + elif method == "POST" and toggle_post_method: + if bulk_re.match(path): + G = None + bulk = None + for i, elem in enumerate(msg.value.split("\n")): + if elem != "": + elem = json.loads(elem) + if i == 0: + graph = elem.get("graph") + self.curGraph = graph + logger.info(f"Setting self.curGraph to: {self.curGraph}") + G = self._conn.graph(graph) + bulk = G.bulkAdd() + vertex = elem.get("vertex", None) + if vertex is not None: + bulk.addVertex(id=str(vertex.get("id")), label=str(vertex.get("label")), data=dict(vertex.get("data"))) + edge = elem.get("edge", None) + if edge is not None: + bulk.addEdge(src=str(edge.get("from")), dst=str(edge.get("to")), id=str(edge.get("id")), label=str(edge.get("label")), data=dict(edge.get("data"))) + if bulk is not None: + err = bulk.execute() + assert err['errorCount'] == 0, f"Err: {err}" + elif add_vertex_re.match(path): + graph_name = path.split('/')[-2] + value = msg.value + logger.info(f"Adding vertex {value['id']} to graph {graph_name}") + self._conn.graph(graph_name).addVertex( + id=value["id"], + label=value["label"], + data=value["data"] + ) + elif add_edge_re.match(path): + graph_name = path.split('/')[-2] + value = msg.value + logger.info(f"Adding edge {value['id']} to graph {graph_name}") + self._conn.graph(graph_name).addEdge( + src=value["from"], + dst=value["to"], + data=value["data"], + id=value["id"], + label=value["label"] + ) + elif graph_re.match(path): + graph_name = path.split('/')[-1] + if graph_name not in created_graphs: + logger.info(f"CALLING ADD GRAPH +++++++++++++++++++++++++++++++++++++++ for {graph_name}") + self._conn.addGraph(graph_name) + created_graphs.add(graph_name) + else: + logger.info(f"Skipping duplicate ADD GRAPH for {graph_name}") + + # Verify graph data + if toggle_post_method: + G = self._conn.graph(self.curGraph) + vertex_count_result = list(G.V().count()) + vertex_count = vertex_count_result[0]['count'] + assert vertex_count > 0, f"No vertices loaded into {self.curGraph}" + edge_count_result = list(G.V().outE().count()) + edge_count = edge_count_result[0]['count'] if edge_count_result else 0 + if orig_vertex_counts is not None and orig_edge_counts is not None: + assert orig_vertex_counts == vertex_count, f"original_vertex_counts {orig_vertex_counts} != vertex_count {vertex_count}" + assert orig_edge_counts == edge_count, f"original_edge_counts {orig_edge_counts} != edge_count {edge_count}" + logger.info("assert statements passed") + logger.info(f"Loaded {vertex_count} vertices and {edge_count} edges") + self._conn.deleteGraph(self.curGraph) + G = self._conn.graph(self.curGraph) + try: + vertex_count_result = list(G.V().count()) + except Exception as e: + assert "was not found" in str(e) + + +def main(): + conn = create_connection(GRIP_SERVER_URL, None, None) + if not conn: + sys.exit(1) + + manager = KafkaManager(conn=conn, readOnly=False, server=GRIP_SERVER_URL) + errors, edges, vertices = manager.test_bulk_load_test_graph() + manager.test_write_from_kafka(toggle_delete_method=False, toggle_post_method=True, orig_vertex_counts=len(vertices), orig_edge_counts=len(edges)) + + +if __name__ == "__main__": + main() diff --git a/conformance/run_util.py b/conformance/run_util.py index cc6651244..0b71dc9a8 100644 --- a/conformance/run_util.py +++ b/conformance/run_util.py @@ -147,6 +147,9 @@ def set_connection(self, conn): else: self.user = None + def collect_fields_dict(self, datadict): + return {key: value for key, value in datadict.items() if key not in ["_id", "_label", "_from", "_to"]} + @staticmethod def parse_grip_config(grip_config_file_path): """Parse grip config.""" @@ -158,7 +161,7 @@ def parse_grip_config(grip_config_file_path): def newGraph(self): if self.readOnly is None: - self.curGraph = "test_graph_" + id_generator() + self.curGraph = "test_graph_" + self.id_generator() self._conn.addGraph(self.curGraph) else: self.curGraph = args.readOnly @@ -173,22 +176,26 @@ def setGraph(self, name): if self.curGraph != "": self.clean() - self.curGraph = "test_graph_" + id_generator() + self.curGraph = "test_graph_" + self.id_generator() self._conn.addGraph(self.curGraph) G = self._conn.graph(self.curGraph) + bulk = G.bulkAdd() with open(os.path.join(BASE, "graphs", "%s.vertices" % (name))) as handle: for line in handle: data = json.loads(line) - G.addVertex(data["gid"], data["label"], data.get("data", {})) + + bulk.addVertex(data["_id"], data["_label"], self.collect_fields_dict(data)) with open(os.path.join(BASE, "graphs", "%s.edges" % (name))) as handle: for line in handle: data = json.loads(line) - G.addEdge(src=data["from"], dst=data["to"], - gid=data.get("gid", None), label=data["label"], - data=data.get("data", {})) + bulk.addEdge(src=data["_from"], dst=data["_to"], + id=data.get("_id", None), label=data["_label"], + data=self.collect_fields_dict(data)) + + bulk.execute() self.curName = name return G @@ -201,7 +208,7 @@ def writeTest(self): raise SkipTest self.clean() self.curName = "" - self.curGraph = "test_graph_" + id_generator() + self.curGraph = "test_graph_" + self.id_generator() self._conn.addGraph(self.curGraph) G = self._conn.graph(self.curGraph) return G @@ -275,7 +282,7 @@ def create_graphs_from_policies(self): bulk.addVertex("Foo:1", "Foo", {"bar": "foo-bar"}) err = bulk.execute() assert err['insertCount'] == 1 and err['errorCount'] == 0, f"Did not insert 1 row {err}" - results = [v for v in G.query().V().hasLabel("Foo").count()] + results = [v for v in G.V().hasLabel("Foo").count()] assert results[0]['count'] == 1, f"Could not query Foo vertex. {results}" return graph_names @@ -284,7 +291,7 @@ def test_query(self, graph_name): G = self._conn.graph(graph_name) try: # this raises an HTTP error - results = [v for v in G.query().V().hasLabel("Foo").count().execute()] + results = [v for v in G.V().hasLabel("Foo").count().execute()] assert results[0]['count'] > 0, f"test_query {results}" except requests.HTTPError as e: assert False, f"test_query graph {graph_name} {self.current_user_policies()} {e}" @@ -319,9 +326,9 @@ def current_user_account(self): return next(iter(account for account in self.accounts if account.user == self.user), None) -def id_generator(size=6, chars=string.ascii_uppercase + string.digits): - """Random 6 alpha numeric string.""" - return ''.join(random.choice(chars) for _ in range(size)).lower() + def id_generator(self, size=6, chars=string.ascii_uppercase + string.digits): + """Random 6 alpha numeric string.""" + return ''.join(random.choice(chars) for _ in range(size)).lower() def filter_tests(args, prefix="ot_"): diff --git a/conformance/tests/auth_basic.py b/conformance/tests/auth_basic.py index b22a1ee04..1f9a579fe 100644 --- a/conformance/tests/auth_basic.py +++ b/conformance/tests/auth_basic.py @@ -1,7 +1,5 @@ from __future__ import absolute_import -import requests - def test_current_user_has_policy(manager): """Ensure current user has a policy defined.""" @@ -18,7 +16,7 @@ def test_current_user_can_query(manager): account = manager.current_user_account() assert account, f"Could not find account for {manager.user}" policies = account.policies - assert len(policies) > 0, f"Should have at least one policy" + assert len(policies) > 0, "Should have at least one policy" errors = [] if not account.is_admin: @@ -56,7 +54,7 @@ def test_current_user_can_read(manager): account = manager.current_user_account() assert account, f"Could not find account for {manager.user}" policies = account.policies - assert len(policies) > 0, f"Should have at least one policy" + assert len(policies) > 0, "Should have at least one policy" errors = [] # non admin user if not account.is_admin: @@ -92,7 +90,7 @@ def test_current_user_can_write(manager): account = manager.current_user_account() assert account, f"Could not find account for {manager.user}" policies = account.policies - assert len(policies) > 0, f"Should have at least one policy" + assert len(policies) > 0, "Should have at least one policy" errors = [] # non admin user if not account.is_admin: @@ -109,7 +107,7 @@ def test_current_user_can_write(manager): try: manager.test_write('dummy') errors.append(f"{manager.user} should not be able to write dummy graph") - except AssertionError as e: + except AssertionError: pass else: diff --git a/conformance/tests/ot_aggregations.py b/conformance/tests/ot_aggregations.py index d0553c792..989eac510 100644 --- a/conformance/tests/ot_aggregations.py +++ b/conformance/tests/ot_aggregations.py @@ -21,7 +21,7 @@ def test_simple(man): G = man.setGraph("swapi") count = 0 - for row in G.query().V().aggregate(gripql.term("simple-agg", "eye_color")): + for row in G.V().aggregate(gripql.term("simple-agg", "eye_color")): if row['name'] != 'simple-agg': errors.append("Result had Incorrect aggregation name") return errors @@ -39,7 +39,7 @@ def test_traversal_term_aggregation(man): G = man.setGraph("swapi") count = 0 - for row in G.query().V("Film:1").out().hasLabel("Character").aggregate(gripql.term("traversal-agg", "eye_color")): + for row in G.V("Film:1").out().hasLabel("Character").aggregate(gripql.term("traversal-agg", "eye_color")): if row['name'] != 'traversal-agg': errors.append("Result had Incorrect aggregation name") return errors @@ -73,7 +73,7 @@ def test_traversal_histogram_aggregation(man): } count = 0 - for row in G.query().V("Film:1").out().hasLabel("Character").aggregate(gripql.histogram("traversal-agg", "height", 25)): + for row in G.V("Film:1").out().hasLabel("Character").aggregate(gripql.histogram("traversal-agg", "height", 25)): count += 1 if row['name'] != 'traversal-agg': errors.append("Result had Incorrect aggregation name") @@ -98,7 +98,7 @@ def test_traversal_percentile_aggregation(man): heights = np.array([96, 97, 150, 165, 167, 170, 172, 173, 175, 178, 180, 180, 180, 182, 183, 188, 202, 228]) data = [] - for row in G.query().V("Film:1").out().hasLabel("Character").aggregate(gripql.percentile("traversal-agg", "height", percents)): + for row in G.V("Film:1").out().hasLabel("Character").aggregate(gripql.percentile("traversal-agg", "height", percents)): count += 1 if row['name'] != 'traversal-agg': errors.append("Result had Incorrect aggregation name") @@ -122,7 +122,7 @@ def getMinMax(input_data, percent, accuracy=0.15): if count != len(percents): errors.append( "Unexpected number of terms: %d != %d" % - (len(row["buckets"]), len(percents)) + (len(res["buckets"]), len(percents)) ) return errors @@ -141,7 +141,7 @@ def test_traversal_edge_histogram_aggregation(man): } count = 0 - for row in G.query().V().hasLabel("Film").outE().aggregate(gripql.histogram("edge-agg", "scene_count", 4)): + for row in G.V().hasLabel("Film").outE().aggregate(gripql.histogram("edge-agg", "scene_count", 4)): count += 1 if row['name'] != 'edge-agg': errors.append("Result had Incorrect aggregation name") @@ -171,14 +171,14 @@ def test_traversal_gid_aggregation(man): } count = 0 - for row in G.query().V().hasLabel("Planet").as_("a").out("residents").select("a").aggregate(gripql.term("gid-agg", "_gid")): + for row in G.V().hasLabel("Planet").as_("a").out("residents").select("a").aggregate(gripql.term("id-agg", "_id")): count += 1 - if 'gid-agg' != row['name']: + if 'id-agg' != row['name']: errors.append("Result had Incorrect aggregation name") return errors if planet_agg_map[row["key"]] != row["value"]: - errors.append("Incorrect bucket count returned: %s" % res) + errors.append("Incorrect bucket count returned: %s" % row) if count != 2: errors.append( @@ -190,18 +190,19 @@ def test_traversal_gid_aggregation(man): def test_field_aggregation(man): errors = [] - fields = [ "id", 'orbital_period', 'gravity', 'terrain', 'name','climate', 'system', 'diameter', 'rotation_period', 'url', 'population', 'surface_water'] + # TODO: find way to get gripper driver to drop id field + fields = [ "_id", "id", "_label", 'orbital_period', 'gravity', 'terrain', 'name','climate', 'system', 'diameter', 'rotation_period', 'url', 'population', 'surface_water'] G = man.setGraph("swapi") count = 0 - for row in G.query().V().hasLabel("Planet").aggregate(gripql.field("gid-agg", "$._data")): + for row in G.V().hasLabel("Planet").aggregate(gripql.field("id-agg", "$")): if row["key"] not in fields: errors.append("unknown field returned: %s" % (row['key'])) if row["value"] != 3: errors.append("incorrect count returned: %s" % (row['value'])) count += 1 - if count not in [11, 12]: # gripper returns an id field as well, others dont.... - errors.append("Incorrect number of results returned") + if count not in [11, 12, 13]: # gripper returns an id field as well, others dont.... + errors.append("""V().hasLabel("Planet").aggregate(gripql.field("id-agg", "$")) : Incorrect number of results returned %d""" % (count)) return errors @@ -216,7 +217,7 @@ def test_field_type_aggregation(man): } G = man.setGraph("swapi") count = 0 - for row in G.query().V().hasLabel("Planet").aggregate(list( gripql.type(a) for a in ["population", "name", "gravity", "diameter"])): + for row in G.V().hasLabel("Planet").aggregate(list( gripql.type(a) for a in ["population", "name", "gravity", "diameter"])): if types[row['name']] != row['key']: errors.append("Wrong type: %s != %s" % (types[row['name']], row['key'])) count += 1 @@ -230,7 +231,7 @@ def test_count_aggregation(man): G = man.setGraph("swapi") count = 0 - for row in G.query().V().hasLabel("Planet").aggregate(gripql.count("total")): + for row in G.V().hasLabel("Planet").aggregate(gripql.count("total")): if row["value"] != 3: errors.append("Incorrect count returned") count += 1 diff --git a/conformance/tests/ot_basic.py b/conformance/tests/ot_basic.py index 80b4a8978..fa733bd6e 100644 --- a/conformance/tests/ot_basic.py +++ b/conformance/tests/ot_basic.py @@ -5,27 +5,27 @@ def vertex_compare(val, expected): - if val["gid"] != expected["gid"]: + if val["_id"] != expected["_id"]: return False - if val["label"] != expected["label"]: + if val["_label"] != expected["_label"]: return False - for k in expected['data']: - if expected['data'][k] != val['data'].get(k, None): + for k in expected: + if expected[k] != val.get(k, None): return False return True def edge_compare(val, expected): - if val["gid"] != expected["gid"]: + if val["_id"] != expected["_id"]: return False - if val["to"] != expected["to"]: + if val["_to"] != expected["_to"]: return False - if val["from"] != expected["from"]: + if val["_from"] != expected["_from"]: return False - if val["label"] != expected["label"]: + if val["_label"] != expected["_label"]: return False - for k in expected['data']: - if expected['data'][k] != val['data'].get(k, None): + for k in expected: + if expected[k] != val.get(k, None): return False return True @@ -36,23 +36,21 @@ def test_get_vertex(man): G = man.setGraph("swapi") expected = { - "gid": "Character:1", - "label": "Character", - "data": { - "system": { - "created": "2014-12-09T13:50:51.644000Z", - "edited": "2014-12-20T21:17:56.891000Z" - }, - "name": "Luke Skywalker", - "height": 172, - "mass": 77, - "hair_color": "blond", - "skin_color": "fair", - "eye_color": "blue", - "birth_year": "19BBY", - "gender": "male", - "url": "https://swapi.co/api/people/1/" - } + "_id": "Character:1", + "_label": "Character", + "system": { + "created": "2014-12-09T13:50:51.644000Z", + "edited": "2014-12-20T21:17:56.891000Z" + }, + "name": "Luke Skywalker", + "height": 172, + "mass": 77, + "hair_color": "blond", + "skin_color": "fair", + "eye_color": "blue", + "birth_year": "19BBY", + "gender": "male", + "url": "https://swapi.co/api/people/1/" } try: @@ -79,11 +77,10 @@ def test_get_edge(man): G = man.setGraph("swapi") expected = { - "gid": "Film:1-characters-Character:1", - "label": "characters", - "from": "Film:1", - "to": "Character:1", - "data": {} + "_id": "Film:1-characters-Character:1", + "_label": "characters", + "_from": "Film:1", + "_to": "Character:1", } try: @@ -111,20 +108,20 @@ def test_V(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V(): + for i in G.V(): count += 1 if count != 39: - errors.append("Fail: G.query().V() %s != %s" % (count, 25)) + errors.append("Fail: G.V() %s != %s" % (count, 25)) count = 0 - for i in G.query().V("Character:1"): + for i in G.V("Character:1"): count += 1 - if i['gid'] != "Character:1": + if i["_id"] != "Character:1": errors.append( - "Fail: G.query().V(\"Character:1\") - Wrong vertex %s" % (i['gid']) + "Fail: G.V(\"Character:1\") - Wrong vertex %s" % (i["_id"]) ) if count != 1: - errors.append("Fail: G.query().V(\"Character:1\") %s != %s" % (count, 1)) + errors.append("Fail: G.V(\"Character:1\") %s != %s" % (count, 1)) return errors @@ -135,21 +132,10 @@ def test_E(man): G = man.setGraph("swapi") count = 0 - for i in G.query().E(): + for _ in G.V().outE(): count += 1 if count != 144: - errors.append("Fail: G.query().E() %s != %d" % (count, 144)) - - count = 0 - for i in G.query().E("Film:1-characters-Character:1"): - if i['gid'] != "Film:1-characters-Character:1": - errors.append( - "Fail: G.query().E(\"Film:1-characters-Character:1\") - Wrong edge %s" % (i['gid']) - ) - count += 1 - if count != 1: - errors.append( - "Fail: G.query().E(\"Film:1-characters-Character:1\") %s != %d" % (count, 1)) + errors.append("Fail: G.V().outE() %s != %d" % (count, 144)) return errors @@ -160,38 +146,26 @@ def test_outgoing(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V("Starship:12").out(): - if i['gid'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: + for i in G.V("Starship:12").out(): + if i['_id'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: errors.append( - "Fail: G.query().V(\"Starship:12\").out() - Wrong vertex %s" % (i['gid']) + "Fail: G.V(\"Starship:12\").out() - Wrong vertex %s" % (i['_id']) ) count += 1 if count != 5: errors.append( - "Fail: G.query().V(\"Starship:12\").out() %s != %d" % (count, 5)) + "Fail: G.V(\"Starship:12\").out() %s != %d" % (count, 5)) count = 0 - for i in G.query().V("Starship:12").out("pilots"): - if i['gid'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9']: + for i in G.V("Starship:12").out("pilots"): + if i['_id'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9']: errors.append( - "Fail: G.query().V(\"Starship:12\").out(\"pilots\") - Wrong vertex %s" % (i['gid']) + "Fail: G.V(\"Starship:12\").out(\"pilots\") - Wrong vertex %s" % (i['_id']) ) count += 1 if count != 4: errors.append( - "Fail: G.query().V(\"Starship:12\").out(\"pilots\") %s != %d" % (count, 4) - ) - - count = 0 - for i in G.query().E("Film:1-characters-Character:1").out(): - if i['gid'] != "Character:1": - errors.append( - "Fail: G.query().E(\"Film:1-characters-Character:1\").out() - Wrong vertex %s" % (i['gid']) - ) - count += 1 - if count != 1: - errors.append( - "Fail: G.query().E(\"Film:1-characters-Character:1\").out() %s != %d" % (count, 1) + "Fail: G.V(\"Starship:12\").out(\"pilots\") %s != %d" % (count, 4) ) return errors @@ -203,47 +177,35 @@ def test_incoming(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V("Starship:12").in_(): - if i['gid'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: + for i in G.V("Starship:12").in_(): + if i['_id'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: errors.append( - "Fail: G.query().V(\"Starship:12\").in_() - Wrong vertex %s" % (i['gid']) + "Fail: G.V(\"Starship:12\").in_() - Wrong vertex %s" % (i['_id']) ) count += 1 if count != 5: errors.append( - "Fail: G.query().V(\"Starship:12\").in_() %s != %d" % (count, 5)) + "Fail: G.V(\"Starship:12\").in_() %s != %d" % (count, 5)) count = 0 - for i in G.query().V("Starship:12").in_("starships"): - if i['gid'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: + for i in G.V("Starship:12").in_("starships"): + if i['_id'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: errors.append( - "Fail: G.query().V(\"Starship:12\").in_(\"starships\") - Wrong vertex %s" % (i['gid']) + "Fail: G.V(\"Starship:12\").in_(\"starships\") - Wrong vertex %s" % (i['_id']) ) count += 1 if count != 5: errors.append( - "Fail: G.query().V(\"Starship:12\").in_(\"starships\") %s != %d" % (count, 5) + "Fail: G.V(\"Starship:12\").in_(\"starships\") %s != %d" % (count, 5) ) # sanity check since vertices are connected by multipled edges count = 0 - for i in G.query().V("Starship:12").in_("pilots"): + for i in G.V("Starship:12").in_("pilots"): count += 0 if count != 0: errors.append( - "Fail: G.query().V(\"Starship:12\").in_(\"piolots\") %s != %d" % (count, 0) - ) - - count = 0 - for i in G.query().E("Film:1-characters-Character:1").in_(): - if i['gid'] != "Film:1": - errors.append( - "Fail: G.query().E(\"Film:1-characters-Character:1\").in_() - Wrong vertex %s" % (i['gid']) - ) - count += 1 - if count != 1: - errors.append( - "Fail: G.query().E(\"Film:1-characters-Character:1\").in_() %s != %d" % (count, 1) + "Fail: G.V(\"Starship:12\").in_(\"piolots\") %s != %d" % (count, 0) ) return errors @@ -254,23 +216,23 @@ def test_outgoing_edge(man): G = man.setGraph("swapi") - c = G.query().V("Character:1").outE().count().execute()[0]["count"] + c = G.V("Character:1").outE().count().execute()[0]["count"] if c != 4: - errors.append("Fail: G.query().V(\"Character:1\").outE().count() %d != %d" % (c, 4)) + errors.append("Fail: G.V(\"Character:1\").outE().count() %d != %d" % (c, 4)) - for i in G.query().V("Character:1").outE(): - if not i['gid'].startswith("Character:1"): - errors.append("Fail: G.query().V(\"Character:1\").outE() - \ - Wrong edge '%s'" % (i['gid'])) + for i in G.V("Character:1").outE(): + if not i['_id'].startswith("Character:1"): + errors.append("Fail: G.V(\"Character:1\").outE() - \ + Wrong edge '%s'" % (i['_id'])) - for i in G.query().V("Character:1").outE().out(): - if i['gid'] not in ['Film:1', 'Planet:1', 'Species:1', 'Starship:12']: - errors.append("Fail: G.query().V(\"Character:1\").outE().out() - \ - Wrong vertex %s" % (i['gid'])) + for i in G.V("Character:1").outE().out(): + if i['_id'] not in ['Film:1', 'Planet:1', 'Species:1', 'Starship:12']: + errors.append("Fail: G.V(\"Character:1\").outE().out() - \ + Wrong vertex %s" % (i['_id'])) - c = G.query().V("Character:1").outE("homeworld").count().execute()[0]["count"] + c = G.V("Character:1").outE("homeworld").count().execute()[0]["count"] if c != 1: - errors.append("Fail: G.query().V(\"Character:1\").outE(\"homeworld\").count() - %s != %s" % (c, 1)) + errors.append("Fail: G.V(\"Character:1\").outE(\"homeworld\").count() - %s != %s" % (c, 1)) return errors @@ -280,23 +242,23 @@ def test_incoming_edge(man): G = man.setGraph("swapi") - c = G.query().V("Character:1").inE().count().execute()[0]["count"] + c = G.V("Character:1").inE().count().execute()[0]["count"] if c != 4: - errors.append("Fail: G.query().V(\"Character:1\").inE().count() %d != %d" % (c, 4)) + errors.append("Fail: G.V(\"Character:1\").inE().count() %d != %d" % (c, 4)) - for i in G.query().V("Character:1").inE(): - if not i['gid'].endswith("Character:1"): - errors.append("Fail: G.query().V(\"Character:1\").inE() - \ - Wrong edge %s" % (i['gid'])) + for i in G.V("Character:1").inE(): + if not i['_id'].endswith("Character:1"): + errors.append("Fail: G.V(\"Character:1\").inE() - \ + Wrong edge %s" % (i['_id'])) - for i in G.query().V("Character:1").inE().in_(): - if i['gid'] not in ['Film:1', 'Planet:1', 'Species:1', 'Starship:12']: - errors.append("Fail: G.query().V(\"Character:1\").inE().in() - \ - Wrong vertex %s" % (i['gid'])) + for i in G.V("Character:1").inE().in_(): + if i['_id'] not in ['Film:1', 'Planet:1', 'Species:1', 'Starship:12']: + errors.append("Fail: G.V(\"Character:1\").inE().in() - \ + Wrong vertex %s" % (i['_id'])) - c = G.query().V("Character:1").inE("residents").count().execute()[0]["count"] + c = G.V("Character:1").inE("residents").count().execute()[0]["count"] if c != 1: - errors.append("Fail: G.query().V(\"Character:1\").inE(\"residents\").count() - %s != %s" % (c, 1)) + errors.append("Fail: G.V(\"Character:1\").inE(\"residents\").count() - %s != %s" % (c, 1)) return errors @@ -304,35 +266,35 @@ def test_incoming_edge(man): def test_outgoing_edge_all(man): errors = [] G = man.setGraph("swapi") - for i in G.query().V().as_("a").outE().as_("b").render(["$a._gid", "$b._from", "$b._to", "$b._gid"]): + for i in G.V().as_("a").outE().as_("b").render(["$a._id", "$b._from", "$b._to", "$b._id"]): if i[0] != i[1]: - errors.append("outE _gid/from missmatch %s != %s" % (i[0], i[1])) + errors.append("outE _id/from missmatch %s != %s" % (i[0], i[1])) if i[1] == i[2]: errors.append("outE to/from the same %s == %s" % (i[1], i[2])) if not i[3].startswith(i[0]): - errors.append("outE _gid prefix %s != %s" % (i[3], i[0])) + errors.append("outE _id prefix %s != %s" % (i[3], i[0])) return errors def test_incoming_edge_all(man): errors = [] G = man.setGraph("swapi") - for i in G.query().V().as_("a").inE().as_("b").render(["$a._gid", "$b._to", "$b._gid"]): + for i in G.V().as_("a").inE().as_("b").render(["$a._id", "$b._to", "$b._id"]): if i[0] != i[1]: - errors.append("inE _gid/to missmatch %s != %s" % (i[0], i[1])) + errors.append("inE _id/to missmatch %s != %s" % (i[0], i[1])) if not i[2].endswith(i[0]): - errors.append("inE _gid wrong suffix %s != %s" % (i[2], i[0])) + errors.append("inE _id wrong suffix %s != %s" % (i[2], i[0])) return errors def test_out_edge_out_all(man): errors = [] G = man.setGraph("swapi") - for i in G.query().V().as_("a").outE().as_("b").out().as_("c").render(["$a._gid", "$b._from", "$b._to", "$c._gid"]): + for i in G.V().as_("a").outE().as_("b").out().as_("c").render(["$a._id", "$b._from", "$b._to", "$c._id"]): if i[0] != i[1]: - errors.append("outE-out _gid/from missmatch %s != %s" % (i[0], i[1])) + errors.append("outE-out _id/from missmatch '%s' != '%s'" % (i[0], i[1])) if i[2] != i[3]: - errors.append("outE-out to/_gid missmatch %s != %s" % (i[0], i[1])) + errors.append("outE-out to/_id missmatch '%s' != '%s'" % (i[2], i[3])) return errors @@ -340,11 +302,11 @@ def test_in_out_equal(man): G = man.setGraph("swapi") errors = [] count1 = 0 - for i in G.query().V().out(): + for i in G.V().out(): count1 += 1 count2 = 0 - for i in G.query().V().in_(): + for i in G.V().in_(): count2 += 1 if count1 != count2: @@ -356,11 +318,11 @@ def test_ine_oute_equal(man): G = man.setGraph("swapi") errors = [] count1 = 0 - for i in G.query().V().outE(): + for i in G.V().outE(): count1 += 1 count2 = 0 - for i in G.query().V().inE(): + for i in G.V().inE(): count2 += 1 if count1 != count2: @@ -374,41 +336,28 @@ def test_both(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V("Starship:12").both(): - if i['gid'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: + for i in G.V("Starship:12").both(): + if i['_id'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: errors.append( - "Fail: G.query().V(\"Starship:12\").both() - \ - Wrong vertex %s" % (i['gid']) + "Fail: G.V(\"Starship:12\").both() - \ + Wrong vertex %s" % (i['_id']) ) count += 1 if count != 10: errors.append( - "Fail: G.query().V(\"Starship:12\").both() %s != %d" % (count, 10)) + "Fail: G.V(\"Starship:12\").both() %s != %d" % (count, 10)) count = 0 - for i in G.query().V("Starship:12").both(["pilots", "starships"]): - if i['gid'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: + for i in G.V("Starship:12").both(["pilots", "starships"]): + if i['_id'] not in ['Character:1', 'Character:18', 'Character:19', 'Character:9', 'Film:1']: errors.append( - "Fail: G.query().V(\"Starship:12\").both([\"pilots\", \"starships\"]) - \ - Wrong vertex %s" % (i['gid']) + "Fail: G.V(\"Starship:12\").both([\"pilots\", \"starships\"]) - \ + Wrong vertex %s" % (i['_id']) ) count += 1 if count != 9: errors.append( - "Fail: G.query().V(\"Starship:12\").both([\"pilots\", \"starships\"]) %s != %d" % (count, 9) - ) - - count = 0 - for i in G.query().E("Film:1-characters-Character:1").both(): - if i['gid'] not in ["Film:1", "Character:1"]: - errors.append( - "Fail: G.query().E(\"Film:1-characters-Character:1\").both() - \ - Wrong vertex %s" % (i['gid']) - ) - count += 1 - if count != 2: - errors.append( - "Fail: G.query().E(\"Film:1-characters-Character:1\").both() %s != %d" % (count, 2) + "Fail: G.V(\"Starship:12\").both([\"pilots\", \"starships\"]) %s != %d" % (count, 9) ) return errors @@ -419,23 +368,23 @@ def test_both_edge(man): G = man.setGraph("swapi") - c = G.query().V("Character:1").bothE().count().execute()[0]["count"] + c = G.V("Character:1").bothE().count().execute()[0]["count"] if c != 8: - errors.append("Fail: G.query().V(\"Character:1\").bothE().count() %d != %d" % (c, 8)) + errors.append("Fail: G.V(\"Character:1\").bothE().count() %d != %d" % (c, 8)) - for i in G.query().V("Character:1").inE(): - if not (i['gid'].startswith("Character:1") or i['gid'].endswith("Character:1")): - errors.append("Fail: G.query().V(\"Character:1\").bothE() - \ - Wrong edge %s" % (i['gid'])) + for i in G.V("Character:1").inE(): + if not (i["_id"].startswith("Character:1") or i["_id"].endswith("Character:1")): + errors.append("Fail: G.V(\"Character:1\").bothE() - \ + Wrong edge %s" % (i["_id"])) - for i in G.query().V("Character:1").bothE().out(): - if i['gid'] not in ['Character:1', 'Character:1', 'Character:1', 'Character:1', 'Film:1', 'Planet:1', 'Species:1', 'Starship:12']: - errors.append("Fail: G.query().V(\"Character:1\").bothE().out() - \ - Wrong vertex %s" % (i['gid'])) + for i in G.V("Character:1").bothE().out(): + if i["_id"] not in ['Character:1', 'Character:1', 'Character:1', 'Character:1', 'Film:1', 'Planet:1', 'Species:1', 'Starship:12']: + errors.append("Fail: G.V(\"Character:1\").bothE().out() - \ + Wrong vertex %s" % (i["_id"])) - c = G.query().V("Character:1").bothE(["homeworld", "residents"]).count().execute()[0]["count"] + c = G.V("Character:1").bothE(["homeworld", "residents"]).count().execute()[0]["count"] if c != 2: - errors.append("Fail: G.query().V(\"Character:1\").inE([\"homeworld\", \"residents\"]).count() - %s != %s" % (c, 2)) + errors.append("Fail: G.V(\"Character:1\").bothE([\"homeworld\", \"residents\"]).count() - %s != %s" % (c, 2)) return errors @@ -446,18 +395,18 @@ def test_limit(man): G = man.setGraph("swapi") tests = [ - "G.query().V().limit(3)", - "G.query().E().limit(3)" + "G.V().limit(3)", + "G.V().outE().limit(3)" ] expected_results = [ - list(i["gid"] for i in G.query().V().execute())[:3], - list(i["gid"] for i in G.query().E().execute())[:3] + list(i["_id"] for i in G.V().execute())[:3], + list(i["_id"] for i in G.V().outE().execute())[:3] ] for test, expected in zip(tests, expected_results): results = eval(test).execute() - actual = [x["gid"] for x in results] + actual = [x["_id"] for x in results] # check contents for x in actual: @@ -483,18 +432,16 @@ def test_skip(man): G = man.setGraph("swapi") tests = [ - "G.query().V().skip(3).limit(3)", - "G.query().E().skip(3).limit(3)" + "G.V().skip(3).limit(3)", ] expected_results = [ - list(i["gid"] for i in G.query().V().execute())[3:6], - list(i["gid"] for i in G.query().E().execute())[3:6] + list(i["_id"] for i in G.V().execute())[3:6], ] for test, expected in zip(tests, expected_results): results = eval(test).execute() - actual = [x["gid"] for x in results] + actual = [x["_id"] for x in results] # check contents for x in actual: @@ -520,22 +467,18 @@ def test_range(man): G = man.setGraph("swapi") tests = [ - "G.query().V().range(3, 5)", - "G.query().V().range(34, -1)", - "G.query().E().range(120, 123)", - "G.query().E().range(140, -1)" + "G.V().range(3, 5)", + "G.V().range(34, -1)", ] expected_results = [ - list(i["gid"] for i in G.query().V().execute())[3:5], - list(i["gid"] for i in G.query().V().execute())[34:], - list(i["gid"] for i in G.query().E().execute())[120:123], - list(i["gid"] for i in G.query().E().execute())[140:] + list(i["_id"] for i in G.V().execute())[3:5], + list(i["_id"] for i in G.V().execute())[34:], ] for test, expected in zip(tests, expected_results): results = eval(test).execute() - actual = [x["gid"] for x in results] + actual = [x["_id"] for x in results] # check contents for x in actual: diff --git a/conformance/tests/ot_bulk.py b/conformance/tests/ot_bulk.py index bf12556e3..eaa2cd189 100644 --- a/conformance/tests/ot_bulk.py +++ b/conformance/tests/ot_bulk.py @@ -1,5 +1,3 @@ - - def test_bulkload(man): errors = [] @@ -22,18 +20,18 @@ def test_bulkload(man): bulk.addEdge("4", "5", "created", {"weight": 1.0}) err = bulk.execute() - print(err) + if err.get("errorCount", 0) != 0: print(err) errors.append("Bulk insertion error") - res = G.query().V().count().execute()[0] + res = G.V().count().execute()[0] if res["count"] != 6: errors.append( "Bulk Add wrong number of vertices: %s != %s" % (res["count"], 6)) - res = G.query().E().count().execute()[0] + res = G.V().outE().count().execute()[0] if res["count"] != 6: errors.append( "Bulk Add wrong number of edges: %s != %s" % @@ -64,8 +62,59 @@ def test_bulkload_validate(man): bulk.addEdge("4", "5", None, {"weight": 1.0}) err = bulk.execute() - if err["errorCount"] == 0: errors.append("Validation error not detected") - print(err) + + return errors + + +def test_bulk_delete(man): + errors = [] + G = man.writeTest() + + G.addVertex("vertex1", "Person", {"name": "marko", "age": "29"}) + G.addVertex("vertex2", "Person", {"name": "vadas", "age": "27"}) + G.addVertex("vertex3", "Software", {"name": "lop", "lang": "java"}) + G.addVertex("vertex4", "Person", {"name": "josh", "age": "32"}) + G.addVertex("vertex5", "Software", {"name": "ripple", "lang": "java"}) + G.addVertex("vertex6", "Person", {"name": "peter", "age": "35"}) + + G.addEdge("vertex1", "vertex3", "created", {"weight": 0.4}, id="edge1") + G.addEdge("vertex1", "vertex2", "knows", {"weight": 0.5}, id="edge2") + G.addEdge("vertex1", "vertex4", "knows", {"weight": 1.0}, id="edge3") + G.addEdge("vertex4", "vertex3", "created", {"weight": 0.4}, id="edge4") + G.addEdge("vertex6", "vertex3", "created", {"weight": 0.2}, id="edge5") + G.addEdge("vertex3", "vertex5", "created", {"weight": 1.0}, id="edge6") + G.addEdge("vertex6", "vertex5", "created", {"weight": 1.0}, id="edge7") + G.addEdge("vertex4", "vertex5", "created", {"weight": 0.4}, id="edge8") + G.addEdge("vertex4", "vertex6", "created", {"weight": 0.4}, id="edge9") + + G.delete(vertices=["vertex1", "vertex2", + "vertex3"], + edges=[]) + + Ecount = G.V().outE().count().execute()[0]["count"] + Vcount = G.V().count().execute()[0]["count"] + if Ecount != 3: + errors.append(f"Wrong number of edges {Ecount} != 3") + if Vcount != 3: + errors.append(f"Wrong number of vertices {Vcount} != 3") + + G.delete(vertices=[], edges=["edge7"]) + Ecount = G.V().outE().count().execute()[0]["count"] + Vcount = G.V().count().execute()[0]["count"] + if Ecount != 2: + errors.append(f"Wrong number of edges {Ecount} != 2") + if Vcount != 3: + errors.append(f"Wrong number of vertices {Vcount} != 3") + + + G.delete(vertices=["vertex5", "vertex6"], edges=["edge9"]) + Ecount = G.V().outE().count().execute()[0]["count"] + Vcount = G.V().count().execute()[0]["count"] + if Ecount != 0: + errors.append(f"Wrong number of edges {Ecount} != 0") + if Vcount != 1: + errors.append(f"Wrong number of vertices {Vcount} != 1") + return errors diff --git a/conformance/tests/ot_bulk_raw.py b/conformance/tests/ot_bulk_raw.py new file mode 100644 index 000000000..14471d656 --- /dev/null +++ b/conformance/tests/ot_bulk_raw.py @@ -0,0 +1,72 @@ +import json + +def load_json_schema(path): + with open(path, 'r') as file: + content = file.read() + return json.loads(content) + + +def test_bulk_add_raw(man): + errors = [] + + G = man.writeTest() + G.addJsonSchema(load_json_schema("conformance/graphs/prompt-schema.json")) + + fetchedSchema = G.getSchema() + len_vertices = len(fetchedSchema['vertices']) + if len_vertices != 2: + errors.append(f"incorrect number of vertices in schema {len_vertices} != 2") + + bulkRaw = G.bulkAddRaw() + bulkRaw.addJson(data={"id": "prompt:0", "resourceType":"Prompt", "text": "Identify the drug-target interactions in the passage given below (along with the interaction type among the following: 'inhibitor', 'agonist', 'modulator', 'activator', 'blocker', 'inducer', 'antagonist', 'cleavage', 'disruption', 'intercalation', 'inactivator', 'bind', 'binder', 'partial agonist', 'cofactor', 'substrate', 'ligand', 'chelator', 'downregulator', 'other', 'antibody', 'other/unknown'):\n\nInhibition of rat brain monoamine oxidase activities by psoralen and isopsoralen: implications for the treatment of affective disorders. Psoralen and isopsoralen, furocoumarins isolated from the plant Psoralea corylifolia L., were demonstrated to exhibit in vitro inhibitory actions on monoamine oxidase (MAO) activities in rat brain mitochondria, preferentially inhibiting MAO-A activity over MAO-B activity. This inhibition of enzyme activities was found to be dose-dependent and reversible. For MAO-A, the IC50 values are 15.2 +/- 1.3 microM psoralen and 9.0 +/- 0.6 microM isopsoralen. For MAO-B, the IC50 values are 61.8 +/- 4.3 microM psoralen and 12.8 +/- 0.5 microM isopsoralen. Lineweaver-Burk transformation of the inhibition data indicates that inhibition by both psoralen and isopsoralen is non-competitive for MAO-A. The Ki values were calculated to be 14.0 microM for psoralen and 6.5 microM for isopsoralen. On the other hand, inhibition by both psoralen and isopsoralen is competitive for MAO-B. The Ki values were calculated to be 58.1 microM for psoralen and 10.8 microM for isopsoralen. These inhibitory actions of psoralen and isopsoralen on rat brain mitochondrial MAO activities are discussed in relation to their toxicities and their potential applications to treat affective disorders.\n", "responses": ["response:0"]}) + bulkRaw.addJson(data={"id": "prompt:1", "resourceType":"Prompt", "text": "Identify the drug-target interactions in the passage given below (along with the interaction type among the following: 'inhibitor', 'agonist', 'modulator', 'activator', 'blocker', 'inducer', 'antagonist', 'cleavage', 'disruption', 'intercalation', 'inactivator', 'bind', 'binder', 'partial agonist', 'cofactor', 'substrate', 'ligand', 'chelator', 'downregulator', 'other', 'antibody', 'other/unknown'):\n\nSelective inhibitor of Janus tyrosine kinase 3, PNU156804, prolongs allograft survival and acts synergistically with cyclosporine but additively with rapamycin.\tJanus kinase 3 (Jak3) is a cytoplasmic tyrosine (Tyr) kinase associated with the interleukin-2 (IL-2) receptor common gamma chain (gamma(c)) that is activated by multiple T-cell growth factors (TCGFs) such as IL-2, -4, and -7. Using human T cells, it was found that a recently discovered variant of the undecylprodigiosin family of antibiotics, PNU156804, previously shown to inhibit IL-2-induced cell proliferation, also blocks IL-2-mediated Jak3 auto-tyrosine phosphorylation, activation of Jak3 substrates signal transducers and activators of transcription (Stat) 5a and Stat5b, and extracellular regulated kinase 1 (Erk1) and Erk2 (p44/p42). Although PNU156804 displayed similar efficacy in blocking Jak3-dependent T-cell proliferation by IL-2, -4, -7, or -15, it was more than 2-fold less effective in blocking Jak2-mediated cell growth, its most homologous Jak family member. A 14-day alternate-day oral gavage with 40 to 120 mg/kg PNU156804 extended the survival of heart allografts in a dose-dependent fashion. In vivo, PNU156804 acted synergistically with the signal 1 inhibitor cyclosporine A (CsA) and additively with the signal 3 inhibitor rapamycin to block allograft rejection. It is concluded that inhibition of signal 3 alone by targeting Jak3 in combination with a signal 1 inhibitor provides a unique strategy to achieve potent immunosuppression.\n", "responses": ["response:1"]}) + bulkRaw.addJson(data={"id": "response:0", "resourceType":"Response", "text": "\n\nDrug: Psoralen\nTarget: Monoamine oxidase (MAO)\nInteraction Type: Inhibitor\n\nDrug: Isopsoralen\nTarget: Monoamine oxidase (MAO)\nInteraction Type: Inhibitor", "prompt": "prompt:0"}) + bulkRaw.addJson(data={"id": "response:1", "resourceType":"Response", "text": "\n\nDrug-target interactions:\n- PNU156804: inhibitor of Jak3\n- Cyclosporine A: signal 1 inhibitor\n- Rapamycin: signal 3 inhibitor\n\nInteraction types:\n- PNU156804: inhibitor\n- Cyclosporine A: inhibitor\n- Rapamycin: inhibitor/signal transduction inhibitor (exact interaction type not specified in the passage)", "prompt": "prompt:1"}) + err = bulkRaw.execute() + + if err["insertCount"] != 4: + errors.append(f"Wrong number of inserted vertices {err['insertCount']} != 4") + if len(err["errors"]) != 0: + errors.append(f"Wrong number of errors {len(err['errors'])} != 0") + + vertex = G.getVertex("prompt:0")['responses'] + if vertex != ['response:0']: + errors.append("prompt:0 responses != ['response:0']") + + labels = G.listLabels() + if not all(item in labels['vertexLabels'] for item in ['Prompt', 'Response']): + errors.append(f"After insert operations {labels} != expected {{'vertexLabels': ['Condition', 'Patient']}}") + + return errors + + + +def test_bulk_add_raw_validation_error(man): + errors = [] + G = man.writeTest() + G.addJsonSchema(load_json_schema("conformance/graphs/prompt-schema.json")) + + bulkRaw = G.bulkAddRaw() + bulkRaw.addJson(data={"id": "prompt:0", "resourceType":"Prompt", "text": "Identify the drug-target interactions in the passage given below (along with the interaction type among the following: 'inhibitor', 'agonist', 'modulator', 'activator', 'blocker', 'inducer', 'antagonist', 'cleavage', 'disruption', 'intercalation', 'inactivator', 'bind', 'binder', 'partial agonist', 'cofactor', 'substrate', 'ligand', 'chelator', 'downregulator', 'other', 'antibody', 'other/unknown'):\n\nInhibition of rat brain monoamine oxidase activities by psoralen and isopsoralen: implications for the treatment of affective disorders. Psoralen and isopsoralen, furocoumarins isolated from the plant Psoralea corylifolia L., were demonstrated to exhibit in vitro inhibitory actions on monoamine oxidase (MAO) activities in rat brain mitochondria, preferentially inhibiting MAO-A activity over MAO-B activity. This inhibition of enzyme activities was found to be dose-dependent and reversible. For MAO-A, the IC50 values are 15.2 +/- 1.3 microM psoralen and 9.0 +/- 0.6 microM isopsoralen. For MAO-B, the IC50 values are 61.8 +/- 4.3 microM psoralen and 12.8 +/- 0.5 microM isopsoralen. Lineweaver-Burk transformation of the inhibition data indicates that inhibition by both psoralen and isopsoralen is non-competitive for MAO-A. The Ki values were calculated to be 14.0 microM for psoralen and 6.5 microM for isopsoralen. On the other hand, inhibition by both psoralen and isopsoralen is competitive for MAO-B. The Ki values were calculated to be 58.1 microM for psoralen and 10.8 microM for isopsoralen. These inhibitory actions of psoralen and isopsoralen on rat brain mitochondrial MAO activities are discussed in relation to their toxicities and their potential applications to treat affective disorders.\n", "responses": ["response:0"]}) + bulkRaw.addJson(data={"id": ["prompt:1"], "resourceType":"Prompt", "text": "Identify the drug-target interactions in the passage given below (along with the interaction type among the following: 'inhibitor', 'agonist', 'modulator', 'activator', 'blocker', 'inducer', 'antagonist', 'cleavage', 'disruption', 'intercalation', 'inactivator', 'bind', 'binder', 'partial agonist', 'cofactor', 'substrate', 'ligand', 'chelator', 'downregulator', 'other', 'antibody', 'other/unknown'):\n\nSelective inhibitor of Janus tyrosine kinase 3, PNU156804, prolongs allograft survival and acts synergistically with cyclosporine but additively with rapamycin.\tJanus kinase 3 (Jak3) is a cytoplasmic tyrosine (Tyr) kinase associated with the interleukin-2 (IL-2) receptor common gamma chain (gamma(c)) that is activated by multiple T-cell growth factors (TCGFs) such as IL-2, -4, and -7. Using human T cells, it was found that a recently discovered variant of the undecylprodigiosin family of antibiotics, PNU156804, previously shown to inhibit IL-2-induced cell proliferation, also blocks IL-2-mediated Jak3 auto-tyrosine phosphorylation, activation of Jak3 substrates signal transducers and activators of transcription (Stat) 5a and Stat5b, and extracellular regulated kinase 1 (Erk1) and Erk2 (p44/p42). Although PNU156804 displayed similar efficacy in blocking Jak3-dependent T-cell proliferation by IL-2, -4, -7, or -15, it was more than 2-fold less effective in blocking Jak2-mediated cell growth, its most homologous Jak family member. A 14-day alternate-day oral gavage with 40 to 120 mg/kg PNU156804 extended the survival of heart allografts in a dose-dependent fashion. In vivo, PNU156804 acted synergistically with the signal 1 inhibitor cyclosporine A (CsA) and additively with the signal 3 inhibitor rapamycin to block allograft rejection. It is concluded that inhibition of signal 3 alone by targeting Jak3 in combination with a signal 1 inhibitor provides a unique strategy to achieve potent immunosuppression.\n", "responses": ["response:1"]}) + bulkRaw.addJson(data={"id": "response:0", "resourceType":"Response", "text": "\n\nDrug: Psoralen\nTarget: Monoamine oxidase (MAO)\nInteraction Type: Inhibitor\n\nDrug: Isopsoralen\nTarget: Monoamine oxidase (MAO)\nInteraction Type: Inhibitor", "prompt": "prompt:0"}) + bulkRaw.addJson(data={"id": "response:1", "resourceType":"Response", "text": "\n\nDrug-target interactions:\n- PNU156804: inhibitor of Jak3\n- Cyclosporine A: signal 1 inhibitor\n- Rapamycin: signal 3 inhibitor\n\nInteraction types:\n- PNU156804: inhibitor\n- Cyclosporine A: inhibitor\n- Rapamycin: inhibitor/signal transduction inhibitor (exact interaction type not specified in the passage)", "prompt": "prompt:1"}) + + err = bulkRaw.execute() + if err['insertCount'] != 3 or len(err['errors']) != 1: + errors.append(f"validation error causes -1 insertCount +1 error: {err['insertCount']} != 3 or {len(err['errors'])} != 1") + + return errors + + +def test_bulk_add_raw_no_schema(man): + errors = [] + + G = man.writeTest() + bulkRaw = G.bulkAddRaw() + bulkRaw.addJson(data={"category":[{"coding":[{"code":"encounter-diagnosis","display":"Encounter Diagnosis","system":"http://terminology.hl7.org/CodeSystem/condition-category"}]}],"clinicalStatus":{"coding":[{"code":"active","system":"http://terminology.hl7.org/CodeSystem/condition-clinical"}]},"code":{"coding":[{"code":"230690007","display":"Stroke","system":"http://snomed.info/sct"}],"text":"Stroke"},"encounter":{"reference":"Encounter/f78a1442-683a-4ea2-adca-161902be19cb"},"id":"838e42fb-a65d-4039-9f83-59c37b1ae889","links":[{"href":"Patient/45c11dad-2b38-4c8e-822e-7abff8a1ee1d","rel":"subject_Patient"}],"meta":{"lastUpdated":"2023-01-26T14:21:56.658+00:00","profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition"],"source":"#DmW9sueQ4yuQdyA9","versionId":"1"},"onsetDateTime":"2013-08-24T15:40:53-04:00","recordedDate":"2013-08-24T15:40:53-04:00","resourceType":"Condition","subject":{"reference":"Patient/45c11dad-2b38-4c8e-822e-7abff8a1ee1d"},"verificationStatus":{"coding":[{"code":"confirmed","system":"http://terminology.hl7.org/CodeSystem/condition-ver-status"}]}}) + err = bulkRaw.execute() + if len(err["errors"]) != 1: + errors.append("No schema was provided so error count should equal 1") + + return [] diff --git a/conformance/tests/ot_bulk_raw_yaml.py b/conformance/tests/ot_bulk_raw_yaml.py new file mode 100644 index 000000000..85d2ae855 --- /dev/null +++ b/conformance/tests/ot_bulk_raw_yaml.py @@ -0,0 +1,42 @@ +import json +import yaml + +def test_post_yaml_schema(man): + errors = [] + G = man.writeTest() + + prompt = process_graph_schema("conformance/graphs/prompt.yaml") + response = process_graph_schema("conformance/graphs/response.yaml") + + G.addSchema(vertices=prompt) + G.addSchema(vertices=response) + + fetchedSchema = G.getSchema() + len_vertices = len(fetchedSchema['vertices']) + if len_vertices != 2: + errors.append(f"incorrect number of vertices in schema {len_vertices} != 2") + + bulkRaw = G.bulkAddRaw() + bulkRaw.addJson(data={"id": "prompt:0", "resourceType":"Prompt", "text": "Identify the drug-target interactions in the passage given below (along with the interaction type among the following: 'inhibitor', 'agonist', 'modulator', 'activator', 'blocker', 'inducer', 'antagonist', 'cleavage', 'disruption', 'intercalation', 'inactivator', 'bind', 'binder', 'partial agonist', 'cofactor', 'substrate', 'ligand', 'chelator', 'downregulator', 'other', 'antibody', 'other/unknown'):\n\nInhibition of rat brain monoamine oxidase activities by psoralen and isopsoralen: implications for the treatment of affective disorders. Psoralen and isopsoralen, furocoumarins isolated from the plant Psoralea corylifolia L., were demonstrated to exhibit in vitro inhibitory actions on monoamine oxidase (MAO) activities in rat brain mitochondria, preferentially inhibiting MAO-A activity over MAO-B activity. This inhibition of enzyme activities was found to be dose-dependent and reversible. For MAO-A, the IC50 values are 15.2 +/- 1.3 microM psoralen and 9.0 +/- 0.6 microM isopsoralen. For MAO-B, the IC50 values are 61.8 +/- 4.3 microM psoralen and 12.8 +/- 0.5 microM isopsoralen. Lineweaver-Burk transformation of the inhibition data indicates that inhibition by both psoralen and isopsoralen is non-competitive for MAO-A. The Ki values were calculated to be 14.0 microM for psoralen and 6.5 microM for isopsoralen. On the other hand, inhibition by both psoralen and isopsoralen is competitive for MAO-B. The Ki values were calculated to be 58.1 microM for psoralen and 10.8 microM for isopsoralen. These inhibitory actions of psoralen and isopsoralen on rat brain mitochondrial MAO activities are discussed in relation to their toxicities and their potential applications to treat affective disorders.\n", "responses": ["response:0"]}) + bulkRaw.addJson(data={"id": "prompt:1", "resourceType":"Prompt", "text": "Identify the drug-target interactions in the passage given below (along with the interaction type among the following: 'inhibitor', 'agonist', 'modulator', 'activator', 'blocker', 'inducer', 'antagonist', 'cleavage', 'disruption', 'intercalation', 'inactivator', 'bind', 'binder', 'partial agonist', 'cofactor', 'substrate', 'ligand', 'chelator', 'downregulator', 'other', 'antibody', 'other/unknown'):\n\nSelective inhibitor of Janus tyrosine kinase 3, PNU156804, prolongs allograft survival and acts synergistically with cyclosporine but additively with rapamycin.\tJanus kinase 3 (Jak3) is a cytoplasmic tyrosine (Tyr) kinase associated with the interleukin-2 (IL-2) receptor common gamma chain (gamma(c)) that is activated by multiple T-cell growth factors (TCGFs) such as IL-2, -4, and -7. Using human T cells, it was found that a recently discovered variant of the undecylprodigiosin family of antibiotics, PNU156804, previously shown to inhibit IL-2-induced cell proliferation, also blocks IL-2-mediated Jak3 auto-tyrosine phosphorylation, activation of Jak3 substrates signal transducers and activators of transcription (Stat) 5a and Stat5b, and extracellular regulated kinase 1 (Erk1) and Erk2 (p44/p42). Although PNU156804 displayed similar efficacy in blocking Jak3-dependent T-cell proliferation by IL-2, -4, -7, or -15, it was more than 2-fold less effective in blocking Jak2-mediated cell growth, its most homologous Jak family member. A 14-day alternate-day oral gavage with 40 to 120 mg/kg PNU156804 extended the survival of heart allografts in a dose-dependent fashion. In vivo, PNU156804 acted synergistically with the signal 1 inhibitor cyclosporine A (CsA) and additively with the signal 3 inhibitor rapamycin to block allograft rejection. It is concluded that inhibition of signal 3 alone by targeting Jak3 in combination with a signal 1 inhibitor provides a unique strategy to achieve potent immunosuppression.\n", "responses": ["response:1"]}) + bulkRaw.addJson(data={"id": "response:0", "resourceType":"Response", "text": "\n\nDrug: Psoralen\nTarget: Monoamine oxidase (MAO)\nInteraction Type: Inhibitor\n\nDrug: Isopsoralen\nTarget: Monoamine oxidase (MAO)\nInteraction Type: Inhibitor", "prompt": "prompt:0"}) + bulkRaw.addJson(data={"id": "response:1", "resourceType":"Response", "text": "\n\nDrug-target interactions:\n- PNU156804: inhibitor of Jak3\n- Cyclosporine A: signal 1 inhibitor\n- Rapamycin: signal 3 inhibitor\n\nInteraction types:\n- PNU156804: inhibitor\n- Cyclosporine A: inhibitor\n- Rapamycin: inhibitor/signal transduction inhibitor (exact interaction type not specified in the passage)", "prompt": "prompt:1"}) + err = bulkRaw.execute() + + if err["insertCount"] != 4: + errors.append(f"Wrong number of inserted vertices {err['insertCount']} != 4") + if len(err["errors"]) != 0: + errors.append(f"Wrong number of errors {len(err['errors'])} != 0") + + vertex = G.getVertex("prompt:0")['responses'] + if vertex != ['response:0']: + errors.append("prompt:0 responses != ['response:0']") + + return errors + +def process_graph_schema(path): + with open(path, 'r') as file: + content = file.read() + vertex = yaml.safe_load(content) + id = vertex["id"].split("/")[-1] + return [{"data":vertex, "label": id, "id": vertex["id"] }] diff --git a/conformance/tests/ot_count.py b/conformance/tests/ot_count.py index 63fab79dd..21fa5e583 100644 --- a/conformance/tests/ot_count.py +++ b/conformance/tests/ot_count.py @@ -5,30 +5,30 @@ def test_count(man): G = man.setGraph("swapi") - i = list(G.query().V().count()) + i = list(G.V().count()) if len(i) < 1: - errors.append("Fail: nothing returned for O.query().V().count()") + errors.append("Fail: nothing returned for O.V().count()") elif i[0]["count"] != 39: - errors.append("Fail: G.query().V().count() %s != %s" % (i[0]["count"], 39)) + errors.append("Fail: G.V().count() %s != %s" % (i[0]["count"], 39)) - i = list(G.query().V("non-existent").count()) - print(i) + i = list(G.V("non-existent").count()) + #print(i) if len(i) < 1: - errors.append("Fail: nothing returned for O.query().V(\"non-existent\").count()") + errors.append("Fail: nothing returned for O.V(\"non-existent\").count()") elif i[0]["count"] != 0: - errors.append("Fail: G.query().V(\"non-existent\").count() %s != %s" % (i[0]["count"], 0)) + errors.append("Fail: G.V(\"non-existent\").count() %s != %s" % (i[0]["count"], 0)) - i = list(G.query().E().count()) + i = list(G.V().outE().count()) if len(i) < 1: - errors.append("Fail: nothing returned for O.query().E().count()") + errors.append("Fail: nothing returned for O.V().outE().count()") elif i[0]["count"] != 144: - errors.append("Fail: G.query().E().count() %s != %s" % (i[0]["count"], 144)) + errors.append("Fail: G.V().outE().count() %s != %s" % (i[0]["count"], 144)) - i = list(G.query().E("non-existent").count()) + i = list(G.V().outE("non-existent").count()) if len(i) < 1: - errors.append("Fail: nothing returned for G.query().E(\"non-existent\").count()") + errors.append("Fail: nothing returned for G.E(\"non-existent\").count()") elif i[0]["count"] != 0: - errors.append("Fail: G.query().E(\"non-existent\").count() %s != %s" % (i[0]["count"], 0)) + errors.append("Fail: G.V().outE(\"non-existent\").count() %s != %s" % (i[0]["count"], 0)) return errors @@ -40,29 +40,29 @@ def test_count_when_no_data(man): G = man.writeTest() - i = list(G.query().V().count()) - print(i) + i = list(G.V().count()) + #print(i) if len(i) < 1: - errors.append("Fail: nothing returned for G.query().V().count()") + errors.append("Fail: nothing returned for G.V().count()") elif i[0]["count"] != 0: - errors.append("Fail: G.query().V().count() %s != %s" % (i[0]["count"], 0)) + errors.append("Fail: G.V().count() %s != %s" % (i[0]["count"], 0)) - i = list(G.query().V("non-existent").count()) + i = list(G.V("non-existent").count()) if len(i) < 1: - errors.append("Fail: nothing returned for G.query().V(\"non-existent\").count()") + errors.append("Fail: nothing returned for G.V(\"non-existent\").count()") elif i[0]["count"] != 0: - errors.append("Fail: G.query().V(\"non-existent\").count() %s != %s" % (i[0]["count"], 0)) + errors.append("Fail: G.V(\"non-existent\").count() %s != %s" % (i[0]["count"], 0)) - i = list(G.query().E().count()) + i = list(G.V().outE().count()) if len(i) < 1: - errors.append("Fail: nothing returned for G.query().E().count()") + errors.append("Fail: nothing returned for G.V().outE().count()") elif i[0]["count"] != 0: - errors.append("Fail: G.query().E().count() %s != %s" % (i[0]["count"], 0)) + errors.append("Fail: G.V().outE().count() %s != %s" % (i[0]["count"], 0)) - i = list(G.query().E("non-existent").count()) + i = list(G.V().outE("non-existent").count()) if len(i) < 1: - errors.append("Fail: nothing returned for G.query().E(\"non-existent\").count()") + errors.append("Fail: nothing returned for G.E(\"non-existent\").count()") elif i[0]["count"] != 0: - errors.append("Fail: G.query().E(\"non-existent\").count() %s != %s" % (i[0]["count"], 0)) + errors.append("Fail: G.V().outE(\"non-existent\").count() %s != %s" % (i[0]["count"], 0)) return errors diff --git a/conformance/tests/ot_distinct.py b/conformance/tests/ot_distinct.py index 4084c009a..a68ccdd0d 100644 --- a/conformance/tests/ot_distinct.py +++ b/conformance/tests/ot_distinct.py @@ -4,46 +4,46 @@ def test_distinct(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().distinct(): + for i in G.V().distinct(): count += 1 if count != 39: - errors.append("Distinct %s != %s" % (count, 39)) + errors.append("V().distinct() distinct count %s != %s" % (count, 39)) count = 0 - for i in G.query().V().distinct("_gid"): + for i in G.V().distinct("_id"): count += 1 if count != 39: - errors.append("Distinct %s != %s" % (count, 39)) + errors.append("""V().distinct("_id") distinct count %s != %s""" % (count, 39)) count = 0 - for i in G.query().V().distinct("eye_color"): + for i in G.V().distinct("eye_color"): count += 1 if count != 8: - errors.append("Distinct %s != %s" % (count, 8)) + errors.append("""V().distinct("eye_color") distinct count %s != %s""" % (count, 8)) count = 0 - for i in G.query().V().distinct("gender"): + for i in G.V().distinct("gender"): count += 1 if count != 4: - errors.append("Distinct %s != %s" % (count, 4)) + errors.append("""V().distinct("gender") distinct count %s != %s""" % (count, 4)) count = 0 - for i in G.query().V().distinct("non-existent-field"): + for i in G.V().distinct("non-existent-field"): count += 1 if count != 0: errors.append("Distinct %s != %s" % (count, 0)) count = 0 - for i in G.query().V().hasLabel("Character").as_("person").out().distinct("$person.name"): + for i in G.V().hasLabel("Character").as_("person").out().distinct("$person.name"): count += 1 if count != 18: - errors.append("Distinct G.query().V().hasLabel(\"Person\").as_(\"person\").out().distinct(\"$person.name\") %s != %s" % (count, 18)) + errors.append("Distinct G.V().hasLabel(\"Character\").as_(\"person\").out().distinct(\"$person.name\") %s != %s" % (count, 18)) count = 0 - for i in G.query().V().hasLabel("Character").as_("person").out().distinct("$person.eye_color"): + for i in G.V().hasLabel("Character").as_("person").out().distinct("$person.eye_color"): count += 1 if count != 8: - errors.append("Distinct G.query().V().hasLabel(\"Person\").as_(\"person\").out().distinct(\"$person.eye_color\") %s != %s" % (count, 8)) + errors.append("Distinct G.V().hasLabel(\"Character\").as_(\"person\").out().distinct(\"$person.eye_color\") %s != %s" % (count, 8)) return errors @@ -55,7 +55,7 @@ def test_distinct_multi(man): count = 0 o = {} - for i in G.query().V().as_("a").out().distinct(["$a.eye_color", "_gid"]).render(["$a.eye_color", "_gid"]): + for i in G.V().as_("a").out().distinct(["$a.eye_color", "_id"]).render(["$a.eye_color", "_id"]): if i[0] in o and o[i[0]] != i[1]: errors.append("Non-unique pair returned: %s" % (i)) count += 1 diff --git a/conformance/tests/ot_error.py b/conformance/tests/ot_error.py index b46f31c99..8f00e9163 100644 --- a/conformance/tests/ot_error.py +++ b/conformance/tests/ot_error.py @@ -6,7 +6,7 @@ def test_fields(man): G = man.setGraph("swapi") try: - for i in G.query().out(): + for i in G.out(): pass errors.append("Bad traversal query returned without exception") except Exception: diff --git a/conformance/tests/ot_fields.py b/conformance/tests/ot_fields.py index 8ad06aa0a..c4d4f65b0 100644 --- a/conformance/tests/ot_fields.py +++ b/conformance/tests/ot_fields.py @@ -5,20 +5,19 @@ def test_fields(man): G = man.setGraph("swapi") expected = { - u"gid": u"Character:1", - u"label": u"Character", - u"data": {u"name": u"Luke Skywalker"} + u"_id": u"Character:1", + u"_label": u"Character", + u"name": u"Luke Skywalker" } - resp = G.query().V("Character:1").fields(["name"]).execute() + resp = G.V("Character:1").fields(["name"]).execute() if resp[0] != expected: - errors.append("vertex contains incorrect fields: \nexpected:%s\nresponse:%s" % (expected, resp)) + errors.append("""Query 'V("Character:1").fields(["name"])' vertex contains incorrect fields: \nexpected:%s\nresponse:%s""" % (expected, resp)) expected = { - u"gid": u"Character:1", - u"label": u"Character", - u"data": {} + u"_id": u"Character:1", + u"_label": u"Character", } - resp = G.query().V("Character:1").fields(["non-existent"]).execute() + resp = G.V("Character:1").fields(["non-existent"]).execute() if resp[0] != expected: errors.append("vertex contains incorrect fields: \nexpected:%s\nresponse:%s" % (expected, resp)) diff --git a/conformance/tests/ot_group.py b/conformance/tests/ot_group.py new file mode 100644 index 000000000..8f88094d1 --- /dev/null +++ b/conformance/tests/ot_group.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import + +import gripql + +def test_childGroups(man): + errors = [] + G = man.setGraph("swapi") + + mapping = { + 'Planet:1' : ['Luke Skywalker', 'C-3PO', 'Darth Vader', 'Owen Lars', 'Beru Whitesun lars', 'R5-D4', 'Biggs Darklighter'], + 'Planet:2' : ['Leia Organa', 'Raymus Antilles'] + } + mapping_hair = { + 'Planet:1' : ["blond", None, "none", "brown, grey", "brown", None, "black"], + 'Planet:2' : ["brown", "brown"] + } + + for i in G.V().hasLabel("Planet").as_("planet").out("residents").as_("character").select("planet").group( {"people" : "$character.name"} ): + #print(i) + if sorted(i["people"]) != sorted(mapping[i["_id"]]): + errors.append("grouped output not equal: %s != %s" % (sorted(i["people"]) , sorted(mapping[i["_id"]]))) + + for i in G.V().hasLabel("Planet").as_("planet").out("residents").as_("character").select("planet").group( + {"people" : "$character.name", "hair":"$character.hair_color"} ): + #print(i) + if sorted(i["people"]) != sorted(mapping[i["_id"]]): + errors.append("grouped output not equal: %s != %s" % (sorted(i["people"]) , sorted(mapping[i["_id"]]))) + + if sorted(i["hair"], key=lambda x: (x is None, x)) != sorted(mapping_hair[i["_id"]], key=lambda x: (x is None, x)): + errors.append("grouped output not equal: %s != %s" % (sorted(i["hair"], key=lambda x: (x is None, x)) , sorted(mapping_hair[i["_id"]], key=lambda x: (x is None, x)))) + return errors diff --git a/conformance/tests/ot_has.py b/conformance/tests/ot_has.py index 89ea77238..d9c4e48fa 100644 --- a/conformance/tests/ot_has.py +++ b/conformance/tests/ot_has.py @@ -9,27 +9,27 @@ def test_hasLabel(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().hasLabel("Vehicle"): + for i in G.V().hasLabel("Vehicle"): count += 1 - if not i['gid'].startswith("Vehicle:"): + if not i["_id"].startswith("Vehicle:"): errors.append("Wrong vertex returned %s" % (i)) if count != 4: errors.append( - "Fail: G.query().V().hasLabel(\"Vehicle\") %s != %s" % + "Fail: G.V().hasLabel(\"Vehicle\") %s != %s" % (count, 4)) - for i in G.query().V().hasLabel("Starship"): - if not i['gid'].startswith("Starship:"): + for i in G.V().hasLabel("Starship"): + if not i["_id"].startswith("Starship:"): errors.append("Wrong vertex returned %s" % (i)) count = 0 - for i in G.query().V().hasLabel(["Vehicle", "Starship"]): - if "name" not in i["data"]: - errors.append("vertex %s returned without data" % (i.gid)) + for i in G.V().hasLabel(["Vehicle", "Starship"]): + if "name" not in i: + errors.append("vertex %s returned without data" % (i._id)) count += 1 if count != 12: errors.append( - "Fail: G.query().V().hasLabel([\"Vehicle\", \"Starship\"]) %s != %s" % + "Fail: G.V().hasLabel([\"Vehicle\", \"Starship\"]) %s != %s" % (count, 12)) return errors @@ -41,23 +41,23 @@ def test_hasKey(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().hasKey("manufacturer"): + for i in G.V().hasKey("manufacturer"): count += 1 - if not i['gid'].startswith("Vehicle:") and not i['gid'].startswith("Starship:"): + if not i["_id"].startswith("Vehicle:") and not i["_id"].startswith("Starship:"): errors.append("Wrong vertex returned %s" % (i)) if count != 12: errors.append( - "Fail: G.query().V().hasKey(\"manufacturer\") %s != %s" % + "Fail: G.V().hasKey(\"manufacturer\") %s != %s" % (count, 12)) count = 0 - for i in G.query().V().hasKey(["hyperdrive_rating", "manufacturer"]): + for i in G.V().hasKey(["hyperdrive_rating", "manufacturer"]): count += 1 - if not i['gid'].startswith("Starship:"): + if not i["_id"].startswith("Starship:"): errors.append("Wrong vertex returned %s" % (i)) if count != 8: errors.append( - "Fail: G.query().V().hasKey([\"hyperdrive_rating\", \"manufacturer\"]) %s != %s" % + "Fail: G.V().hasKey([\"hyperdrive_rating\", \"manufacturer\"]) %s != %s" % (count, 8)) return errors @@ -69,23 +69,23 @@ def test_hasId(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().hasId("Character:1"): + for i in G.V().hasId("Character:1"): count += 1 - if i['gid'] != "Character:1": + if i["_id"] != "Character:1": errors.append("Wrong vertex returned %s" % (i)) if count != 1: errors.append( - "Fail: G.query().V().hasId(\"01\") %s != %s" % + "Fail: G.V().hasId(\"Character:1\") %s != %s" % (count, 1)) count = 0 - for i in G.query().V().hasId(["Character:1", "Character:2"]): + for i in G.V().hasId(["Character:1", "Character:2"]): count += 1 - if i['gid'] not in ["Character:1", "Character:2"]: + if i["_id"] not in ["Character:1", "Character:2"]: errors.append("Wrong vertex returned %s" % (i)) if count != 2: errors.append( - "Fail: G.query().V().hasId([\"Character:1\", \"Character:2\"]) %s != %s" % + "Fail: G.V().hasId([\"Character:1\", \"Character:2\"]) %s != %s" % (count, 2)) return errors @@ -97,73 +97,91 @@ def test_has_eq(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.eq("_gid", "Character:3")): + for i in G.V().has(gripql.eq("_id", "Character:3")): count += 1 - if i['gid'] != "Character:3": + if i["_id"] != "Character:3": errors.append("Wrong vertex returned %s" % (i)) if count != 1: errors.append( - "Fail: G.query().V().has(gripql.eq(\"_gid\", \"Character:3\")) %s != %s" % + "Fail: G.V().has(gripql.eq(\"_id\", \"Character:3\")) %s != %s" % (count, 1)) count = 0 - for i in G.query().V().has(gripql.eq("_label", "Character")): + for i in G.V().has(gripql.eq("_label", "Character")): count += 1 - if i['label'] != "Character": - errors.append("Wrong vertex label %s" % (i['label'])) + if i["_label"] != "Character": + errors.append("Wrong vertex label %s" % (i["_label"])) if count != 18: errors.append( - "Fail: G.query().V().has(gripql.eq(\"_label\", \"person\")) %s != %s" % + "Fail: G.V().has(gripql.eq(\"_label\", \"person\")) %s != %s" % (count, 18)) count = 0 - for i in G.query().V().has(gripql.eq("eye_color", "brown")): + for i in G.V().has(gripql.eq("eye_color", "brown")): count += 1 - if i['gid'] not in ["Character:14", "Character:5", "Character:81", "Character:9"]: + if i["_id"] not in ["Character:14", "Character:5", "Character:81", "Character:9"]: errors.append("Wrong vertex returned %s" % (i)) if count != 4: errors.append( - "Fail: G.query().V().has(gripql.eq(\"eye_color\", \"brown\")) %s != %s" % + "Fail: G.V().has(gripql.eq(\"eye_color\", \"brown\")) %s != %s" % (count, 4)) return errors +def test_has_prev(man): + errors = [] + G = man.setGraph("swapi") + + q = G.V().hasLabel("Character").as_("1").out("homeworld").out("residents") + q = q.has(gripql.neq("$1._id", "$._id")) + count = 0 + for i in q.render(["$1._id", "$._id"]): + if i[0] == i[1]: + errors.append("History based filter failed: %s" % (i[0]) ) + count += 1 + if count < 10: + errors.append("Not enough elements found: %d" % (count)) + return errors + + def test_has_neq(man): errors = [] G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.neq("_gid", "Character:1")): + for i in G.V().has(gripql.neq("_id", "Character:1")): count += 1 - if i['gid'] == "Character:1": + if i["_id"] == "Character:1": errors.append("Wrong vertex returned %s" % (i)) if count != 38: errors.append( - "Fail: G.query().V().has(gripql.not_(gripql.eq(\"_gid\", \"Character:1\"))) %s != %s" % + "Fail: G.V().has(gripql.not_(gripql.eq(\"_id\", \"Character:1\"))) %s != %s" % (count, 38)) count = 0 - for i in G.query().V().has(gripql.neq("_label", "Character")): + for i in G.V().has(gripql.neq("_label", "Character")): count += 1 - if i['label'] == "Character": - errors.append("Wrong vertex label %s" % (i['label'])) + if i["_label"] == "Character": + errors.append("Wrong vertex label %s" % (i["_label"])) if count != 21: errors.append( - "Fail: G.query().V().has(gripql.not_(gripql.eq(\"_label\", \"Character\"))) %s != %s" % + "Fail: G.V().has(gripql.not_(gripql.eq(\"_label\", \"Character\"))) %s != %s" % (count, 21)) + count = 0 - for i in G.query().V().hasLabel("Character").has(gripql.neq("eye_color", "brown")): + for i in G.V().hasLabel("Character").has(gripql.neq("eye_color", "brown")): count += 1 - if i['data']["eye_color"] == "brown": + if i["eye_color"] == "brown": errors.append("Wrong vertex returned %s" % (i)) if count != 14: errors.append( - "Fail: G.query().V().has(gripql.not_(gripql.eq(\"eye_color\", \"brown\"))) %s != %s" % + "Fail: G.V().has(gripql.not_(gripql.eq(\"eye_color\", \"brown\"))) %s != %s" % (count, 14)) + return errors @@ -173,51 +191,87 @@ def test_has_gt(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.gt("height", 202)): + for i in G.V().has(gripql.gt("height", 202)): count += 1 - if i['gid'] not in ["Character:13"]: + if i["_id"] not in ["Character:13"]: errors.append("Wrong vertex returned %s" % (i)) if count != 1: errors.append( - "Fail: G.query().V().has(gripql.gt(\"height\", 200)) %s != %s" % + "Fail: G.V().has(gripql.gt(\"height\", 200)) %s != %s" % (count, 1)) count = 0 - for i in G.query().V().has(gripql.gte("height", 202)): + for i in G.V().has(gripql.gte("height", 202)): count += 1 - if i['gid'] not in ["Character:4", "Character:13"]: + if i["_id"] not in ["Character:4", "Character:13"]: errors.append("Wrong vertex returned %s" % (i)) if count != 2: errors.append( - "Fail: G.query().V().has(gripql.gte(\"height\", 202)) %s != %s" % + "Fail: G.V().has(gripql.gte(\"height\", 202)) %s != %s" % (count, 2)) return errors +def test_has_eq_nil(man): + errors = [] + G = man.setGraph("swapi") + count = 0 + for i in G.V().has(gripql.eq("height", None)): + count += 1 + + if count != 21: + errors.append( + "Fail: G.V().has(gripql.eq(\"height\", None)) %s != %s" % + (count, 21)) + + return errors + + +def test_has_gt_nil(man): + """None, nil translates to 0 when refering to numeric values, + so this evaluates to return All characters with height value greater than 0""" + errors = [] + G = man.setGraph("swapi") + count = 0 + for i in G.V().has(gripql.gt("height", None)): + count += 1 + if count != 0: + errors.append("Fail: G.query.V().has(gripql.gt(\"height\", None)) %s != %s" % (count, 0)) + + return errors + + def test_has_lt(man): errors = [] G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.lt("height", 97)): + + for i in G.V().has(gripql.lt("height", 97)): count += 1 - if i['gid'] not in ["Character:3"]: + + if i["_id"] not in ["Character:3"]: errors.append("Wrong vertex returned %s" % (i)) if count != 1: errors.append( - "Fail: G.query().V().has(gripql.lt(\"height\", 97)) %s != %s" % + "Fail: G.V().has(gripql.lt(\"height\", 97)) %s != %s" % (count, 1)) + return errors + +def test_has_lte(man): + errors = [] + G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.lte("height", 97)): + for i in G.V().has(gripql.lte("height", 97)): count += 1 - if i['gid'] not in ["Character:3", "Character:8"]: + if i["_id"] not in ["Character:3", "Character:8"]: errors.append("Wrong vertex returned %s" % (i)) if count != 2: errors.append( - "Fail: G.query().V().has(gripql.lte(\"height\", 97)) %s != %s" % + "Fail: G.V().has(gripql.lte(\"height\", 97)) %s != %s" % (count, 2)) return errors @@ -229,13 +283,13 @@ def test_has_inside(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.inside("height", 100, 200)): + for i in G.V().has(gripql.inside("height", 100, 200)): count += 1 - if i['gid'] in ["Character:3", "Character:4", "Character:8", "Character:13"]: + if i["_id"] in ["Character:3", "Character:4", "Character:8", "Character:13"]: errors.append("Wrong vertex returned %s" % (i)) if count != 14: errors.append( - "Fail: G.query().V().has(gripql.inside(\"age\", 30, 60)) %s != %s" % + "Fail: G.V().has(gripql.inside(\"age\", 30, 60)) %s != %s" % (count, 14)) return errors @@ -247,13 +301,13 @@ def test_has_outside(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.outside("height", 100, 200)): + for i in G.V().has(gripql.outside("height", 100, 200)): count += 1 - if i['gid'] not in ["Character:3", "Character:4", "Character:8", "Character:13"]: + if i["_id"] not in ["Character:3", "Character:4", "Character:8", "Character:13"]: errors.append("Wrong vertex returned %s" % (i)) if count != 4: errors.append( - "Fail: G.query().V().has(gripql.outside(\"age\", 30, 60)) %s != %s" % + "Fail: G.V().has(gripql.outside(\"age\", 30, 60)) %s != %s" % (count, 4)) return errors @@ -265,13 +319,13 @@ def test_has_between(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.between("height", 180, 200)): + for i in G.V().has(gripql.between("height", 180, 200)): count += 1 - if i['gid'] not in ["Character:10", "Character:12", "Character:14", "Character:19", "Character:81", "Character:9"]: + if i["_id"] not in ["Character:10", "Character:12", "Character:14", "Character:19", "Character:81", "Character:9"]: errors.append("Wrong vertex returned %s" % (i)) if count != 6: errors.append( - "Fail: G.query().V().has(gripql.between(\"height\", 180, 200)) %s != %s" % + "Fail: G.V().has(gripql.between(\"height\", 180, 200)) %s != %s" % (count, 5)) return errors @@ -282,21 +336,21 @@ def test_has_within(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.within("eye_color", ["brown", "hazel"])): + for i in G.V().has(gripql.within("eye_color", ["brown", "hazel"])): count += 1 - if i['gid'] not in ["Character:14", "Character:18", "Character:5", "Character:81", "Character:9"]: + if i["_id"] not in ["Character:14", "Character:18", "Character:5", "Character:81", "Character:9"]: errors.append("Wrong vertex returned %s" % (i)) if count != 5: errors.append( - "Fail: G.query().V().has(gripql.within(\"eye_color\", [\"brown\", \"hazel\"])) %s != %s" % + "Fail: G.V().has(gripql.within(\"eye_color\", [\"brown\", \"hazel\"])) %s != %s" % (count, 5)) count = 0 - for i in G.query().V().has(gripql.within("eye_color", 0)): + for i in G.V().has(gripql.within("eye_color", 0)): count += 1 if count != 0: errors.append( - "Fail: G.query().V().has(gripql.within(\"eye_color\", 0)) %s != %s" % + "Fail: G.V().has(gripql.within(\"eye_color\", 0)) %s != %s" % (count, 0)) return errors @@ -308,21 +362,21 @@ def test_has_without(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.without("eye_color", ["brown"])): + for i in G.V().has(gripql.without("eye_color", ["brown"])): count += 1 - if i['gid'] in ["Character:5", "Character:9", "Character:14", "Character:81"]: + if i["_id"] in ["Character:5", "Character:9", "Character:14", "Character:81"]: errors.append("Wrong vertex returned %s" % (i)) if count != 35: errors.append( - "Fail: G.query().V().has(gripql.without(\"occupation\", [\"jedi\", \"sith\"])) %s != %s" % + """Fail: V().has(gripql.without("eye_color", ["brown"])) %s != %s""" % (count, 35)) count = 0 - for i in G.query().V().has(gripql.without("occupation", 0)): + for i in G.V().has(gripql.without("occupation", 0)): count += 1 if count != 39: errors.append( - "Fail: G.query().V().has(gripql.without(\"occupation\", 0)) %s != %s" % + "Fail: G.V().has(gripql.without(\"occupation\", 0)) %s != %s" % (count, 39)) return errors @@ -334,13 +388,13 @@ def test_has_contains(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.contains("terrain", "jungle")): + for i in G.V().has(gripql.contains("terrain", "jungle")): count += 1 - if i['gid'] not in ["Planet:3"]: + if i["_id"] not in ["Planet:3"]: errors.append("Wrong vertex returned %s" % (i)) if count != 1: errors.append( - "Fail: G.query().V().has(gripql.contains(\"terrain\", \"jungle\")) %s != %s" % + "Fail: G.V().has(gripql.contains(\"terrain\", \"jungle\")) %s != %s" % (count, 1)) return errors @@ -352,13 +406,13 @@ def test_has_and(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.and_(gripql.eq("_label", "Character"), gripql.eq("eye_color", "blue"))): + for i in G.V().has(gripql.and_(gripql.eq("_label", "Character"), gripql.eq("eye_color", "blue"))): count += 1 - if i['gid'] not in ["Character:1", "Character:12", "Character:13", "Character:19", "Character:6", "Character:7"]: + if i["_id"] not in ["Character:1", "Character:12", "Character:13", "Character:19", "Character:6", "Character:7"]: errors.append("Wrong vertex returned %s" % (i)) if count != 6: errors.append( - "Fail: G.query().V().has(gripql.and_(gripql.eq(\"_label\", \"Character\"), gripql.eq(\"eye_color\", \"blue\"))) %s != %s" % + "Fail: G.V().has(gripql.and_(gripql.eq(\"_label\", \"Character\"), gripql.eq(\"eye_color\", \"blue\"))) %s != %s" % (count, 6)) return errors @@ -370,13 +424,13 @@ def test_has_or(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.or_(gripql.eq("eye_color", "blue"), gripql.eq("eye_color", "hazel"))): + for i in G.V().has(gripql.or_(gripql.eq("eye_color", "blue"), gripql.eq("eye_color", "hazel"))): count += 1 - if i['gid'] not in ["Character:1", "Character:12", "Character:13", "Character:18", "Character:19", "Character:6", "Character:7"]: + if i["_id"] not in ["Character:1", "Character:12", "Character:13", "Character:18", "Character:19", "Character:6", "Character:7"]: errors.append("Wrong vertex returned %s" % (i)) if count != 7: errors.append( - "Fail: G.query().V().has(gripql.or_(gripql.eq(\"eye_color\", \"blue\"), gripql.eq(\"eye_color\", \"hazel\"))) %s != %s" % + "Fail: G.V().has(gripql.or_(gripql.eq(\"eye_color\", \"blue\"), gripql.eq(\"eye_color\", \"hazel\"))) %s != %s" % (count, 7)) return errors @@ -388,23 +442,23 @@ def test_has_not(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has(gripql.not_(gripql.eq("_label", "Character"))): + for i in G.V().has(gripql.not_(gripql.eq("_label", "Character"))): count += 1 - if i['gid'].startswith("Character"): + if i["_id"].startswith("Character"): errors.append("Wrong vertex returned %s" % (i)) if count != 21: errors.append( - "Fail: G.query().V().has(gripql.not_(gripql.eq(\"_label\", \"Character\"))) %s != %s" % + "Fail: G.V().has(gripql.not_(gripql.eq(\"_label\", \"Character\"))) %s != %s" % (count, 21)) count = 0 - for i in G.query().V().has(gripql.not_(gripql.neq("_label", "Character"))): + for i in G.V().has(gripql.not_(gripql.neq("_label", "Character"))): count += 1 - if not i['gid'].startswith("Character"): + if not i["_id"].startswith("Character"): errors.append("Wrong vertex returned %s" % (i)) if count != 18: errors.append( - "Fail: G.query().V().has(gripql.not_(gripql.neq(\"_label\", \"Character\"))) %s != %s" % + "Fail: G.V().has(gripql.not_(gripql.neq(\"_label\", \"Character\"))) %s != %s" % (count, 18)) return errors @@ -416,7 +470,7 @@ def test_has_complex(man): G = man.setGraph("swapi") count = 0 - for i in G.query().V().has( + for i in G.V().has( gripql.and_( gripql.eq("_label", "Character"), gripql.not_( @@ -428,16 +482,16 @@ def test_has_complex(man): ) ): count += 1 - if i['label'] == "Character" and (i["data"]["eye_color"] == "brown" or i["data"]["eye_color"] == "hazel"): + if i["_label"] == "Character" and (i["eye_color"] == "brown" or i["eye_color"] == "hazel"): errors.append("Wrong vertex returned %s" % (i)) if count != 13: errors.append( - "Fail: G.query().V().has(gripql.and_(gripql.eq(\"_label\", \"Character\"), gripql.not_(gripql.or_(gripql.eq(\"eye_color\", \"brown\"), gripql.eq(\"eye_color\", \"hazel\"))))) %s != %s" % + "Fail: G.V().has(gripql.and_(gripql.eq(\"_label\", \"Character\"), gripql.not_(gripql.or_(gripql.eq(\"eye_color\", \"brown\"), gripql.eq(\"eye_color\", \"hazel\"))))) %s != %s" % (count, 13) ) count = 0 - for i in G.query().V().has( + for i in G.V().has( gripql.not_( gripql.or_( gripql.eq("_label", "Character"), @@ -446,16 +500,16 @@ def test_has_complex(man): ) ): count += 1 - if i['label'] == "Character" or ("name" in i["data"] and i['data']["name"] == "Human"): + if i["_label"] == "Character" or ("name" in i and i["name"] == "Human"): errors.append("Wrong vertex returned %s" % (i)) if count != 20: errors.append( - "Fail: G.query().V().has(gripql.not_(gripql.and_(gripql.eq(\"_label\", \"Character\"), gripql.eq(\"name\", \"Human\")))) %s != %s" % + "Fail: G.V().has(gripql.not_(gripql.and_(gripql.eq(\"_label\", \"Character\"), gripql.eq(\"name\", \"Human\")))) %s != %s" % (count, 20) ) count = 0 - for i in G.query().V().has( + for i in G.V().has( gripql.not_( gripql.or_( gripql.eq("_label", "Character"), @@ -467,18 +521,18 @@ def test_has_complex(man): ) ): count += 1 - if not i['gid'].startswith("Vehicle:") \ - and not i["gid"].startswith("Starship:") and not i["gid"].startswith("Species:") \ - and not i["gid"].startswith("Planet:") and not i["gid"].startswith("Film:"): + if not i["_id"].startswith("Vehicle:") \ + and not i["_id"].startswith("Starship:") and not i["_id"].startswith("Species:") \ + and not i["_id"].startswith("Planet:") and not i["_id"].startswith("Film:"): errors.append("Wrong vertex returned %s" % (i)) if count != 19: errors.append( - "Fail: G.query().V().has(gripql.not_(gripql.or_(gripql.eq(\"_label\", \"Character\"), gripql.or_(gripql.eq(\"name\", \"Human\"), gripql.contains(\"terrain\", \"jungle\"))))) %s != %s" % + "Fail: G.V().has(gripql.not_(gripql.or_(gripql.eq(\"_label\", \"Character\"), gripql.or_(gripql.eq(\"name\", \"Human\"), gripql.contains(\"terrain\", \"jungle\"))))) %s != %s" % (count, 19) ) count = 0 - for i in G.query().V().has( + for i in G.V().has( gripql.not_( gripql.and_( gripql.eq("_label", "Planet"), @@ -492,7 +546,7 @@ def test_has_complex(man): count += 1 if count != 37: errors.append( - "Fail: G.query().V().has(gripql.not_(gripql.and_(gripql.eq(\"_label\", \"Planet\"), gripql.or_(gripql.eq(\"surface_water\", 1), gripql.contains(\"terrain\", \"jungle\"))))) %s != %s" % + "Fail: G.V().has(gripql.not_(gripql.and_(gripql.eq(\"_label\", \"Planet\"), gripql.or_(gripql.eq(\"surface_water\", 1), gripql.contains(\"terrain\", \"jungle\"))))) %s != %s" % (count, 37) ) diff --git a/conformance/tests/ot_index.py b/conformance/tests/ot_index.py index 991d42dbb..eb4e6f5ce 100644 --- a/conformance/tests/ot_index.py +++ b/conformance/tests/ot_index.py @@ -1,15 +1,40 @@ +import gripql + + +def test_index_create_and_delete(man): + errors = [] + G = man.writeTest() + G.addIndex("Person", "name") + found = False + indices = G.listIndices() + for i in indices: + if i["field"] == "name" and i["label"] == "Person": + found = True + if not found: + errors.append("Expected index to be found") + + G.deleteIndex("Person", "name") + indices = G.listIndices() + + found = False + for i in indices: + if i["field"] == "name" and i["label"] == "Person": + found = True + if found: + errors.append("Expected index not found") + + return errors def test_index(man): errors = [] G = man.writeTest() - G.addIndex("Person", "name") G.addVertex("1", "Person", {"name": "marko", "age": "29"}) G.addVertex("2", "Person", {"name": "vadas", "age": "27"}) - G.addVertex("3", "Software", {"name": "lop", "lang": "java"}) + G.addVertex("3", "Software", {"name": "lop", "lang": "java"}) G.addVertex("4", "Person", {"name": "josh", "age": "32"}) G.addVertex("5", "Software", {"name": "ripple", "lang": "java"}) G.addVertex("6", "Person", {"name": "peter", "age": "35"}) @@ -22,12 +47,201 @@ def test_index(man): G.addEdge("6", "3", "created", {"weight": 0.2}) G.addEdge("4", "5", "created", {"weight": 1.0}) - resp = G.listIndices() + count = 0 + for i in G.V().has(gripql.eq("name","marko")): + count += 1 + if "name" not in i: + errors.append("'name' field not found in vertex") + if i["name"] != "marko": + errors.append("Filtering on field name, value marko but got '%s' instead" % i["name"]) + if count != 2: + errors.append("Expecting 2 vertices returned but got %d instead" % (count)) + + return errors + + +def test_bulk_index(man): + errors = [] + + G = man.writeTest() + G.addIndex("Person", "age") + + bulk = G.bulkAdd() + + bulk.addVertex("1", "Person", {"name": "marko", "age": "29"}) + bulk.addVertex("2", "Person", {"name": "vadas", "age": "27"}) + bulk.addVertex("4", "Person", {"name": "josh", "age": "32"}) + bulk.addVertex("6", "Person", {"name": "peter", "age": "35"}) + bulk.addVertex("7", "Person", {"name": "alice", "age": "31"}) + bulk.addVertex("8", "Person", {"name": "bob", "age": "32"}) + bulk.addVertex("9", "Person", {"name": "charlie", "age": "28"}) + bulk.addVertex("10", "Person", {"name": "diana", "age": "32"}) + bulk.addVertex("11", "Person", {"name": "eve", "age": "30"}) + bulk.addVertex("12", "Person", {"name": "frank", "age": "33"}) + bulk.addVertex("13", "Person", {"name": "grace", "age": "26"}) + bulk.addVertex("14", "Person", {"name": "heidi", "age": "32"}) + bulk.addVertex("15", "Person", {"name": "ivan", "age": "29"}) + bulk.addVertex("16", "Person", {"name": "judy", "age": "34"}) + res = bulk.execute() + if res["errorCount"] > 0: + errors.append("errorCount on bulk add > 0") + + count = 0 + resp3 = G.V().has(gripql.eq("age","32")) + for i in resp3: + count += 1 + if "age" not in i: + errors.append("field 'age' not found in vertex") + if "age" in i and i["age"] != "32": + errors.append("filtering on field age value '32' but got %s instead" % (i["age"])) + if count != 4: + errors.append("expected count 4 but got %d instead" % (count)) + + return errors + + +def test_index_after_write(man): + errors = [] + G = man.writeTest() + bulk = G.bulkAdd() + bulk.addVertex("1", "Person", {"name": "marko", "age": "29"}) + bulk.addVertex("2", "Person", {"name": "vadas", "age": "27"}) + bulk.addVertex("4", "Person", {"name": "josh", "age": "32"}) + bulk.addVertex("6", "Person", {"name": "peter", "age": "35"}) + bulk.addVertex("7", "Person", {"name": "alice", "age": "31"}) + bulk.addVertex("8", "Person", {"name": "bob", "age": "32"}) + bulk.addVertex("9", "Person", {"name": "charlie", "age": "28"}) + bulk.addVertex("10", "Person", {"name": "diana", "age": "32"}) + bulk.addVertex("11", "Person", {"name": "eve", "age": "30"}) + bulk.addVertex("12", "Person", {"name": "frank", "age": "33"}) + bulk.addVertex("13", "Person", {"name": "grace", "age": "26"}) + bulk.addVertex("14", "Person", {"name": "heidi", "age": "32"}) + bulk.addVertex("15", "Person", {"name": "ivan", "age": "29"}) + bulk.addVertex("16", "Person", {"name": "judy", "age": "34"}) + res = bulk.execute() + if res["errorCount"] > 0: + errors.append("errorCount on bulk add > 0") + + G.addIndex("Person", "age") + + count = 0 + restwo = G.V().has(gripql.within("name", ["marko", "vadas", "eve", "ivan", "charlie", "nothere"])) + for i in restwo: + count += 1 + if count != 5: + errors.append("Expected 5 names from filter but got %d instead" % (count)) + + return errors + + +def test_index_filter(man): + errors = [] + G = man.setGraph("swapi") + G.addIndex("Starship", "cost_in_credits") + G.addIndex("Starship", "cargo_capacity") + + respthree = G.V().hasLabel("Starship").has(gripql.lt("cost_in_credits", 150000000)) + count = 0 + for i in respthree: + count += 1 + if i['cost_in_credits'] > 149999999: + errors.append("filtering on ships that cost less than 150000000, but %d > 149999999" % (i['cost_in_credits'])) + + if count != 5: + errors.append("Expected 5 results got %d instead" % (count)) + + + indices = G.listIndices() found = False - for i in resp: - if i["field"] == "name" and i["label"] == "Person": + count = 0 + for i in indices: + count +=1 + if i["field"] == "cost_in_credits" and i["label"] == "Starship": found = True if not found: errors.append("Expected index not found") + if count != 2: + errors.append("Expected to find 2 indices but found %d instead" % (count)) + + G.deleteIndex("Starship", "cost_in_credits") + count = 0 + indices_two = G.listIndices() + found = False + for i in indices_two: + count += 1 + if i["field"] == "cost_in_credits" and i["label"] == "Starship": + found = True + if found: + errors.append("Expected index not found, but it was found") + if count != 1: + errors.append("Expected to find 1 index but found %d instead" % (count)) + + return errors + + +def test_consistent_results(man): + errors = [] + G = man.setGraph("swapi") + + resp = G.V().has(gripql.contains("eye_colors", "yellow")) + count = 0 + for i in resp: + count += 1 + if count != 2: + errors.append("Expected 2 results but got %d instead" % (count)) + + G.addIndex("Species", "eye_colors") + resp = G.V().has(gripql.contains("eye_colors", "yellow")) + count = 0 + for i in resp: + count += 1 + if count != 2: + errors.append("Expected 2 results but got %d instead" % (count)) + + return errors + + +def test_hasLabel_contains(man): + # If using the grids driver + no indexing this test uses the optimized scan function pipeline + errors = [] + G = man.setGraph("swapi") + resp = G.V().hasLabel("Species").has(gripql.contains("eye_colors", "yellow")) + count = 0 + for i in resp: + count += 1 + if count != 2: + errors.append("Expected 2 results but got %d instead" % (count)) + + return errors + + +def test_multiple_labels_and_indices(man): + """ In this scenario both Starship:13 and Vehicle:6 and Vehicle:8 have the speed of 1200 + but since only one index has been declared, in the grids driver the general v.has() should be used""" + + errors = [] + G = man.setGraph("swapi") + G.addIndex("Vehicle", "max_atmosphering_speed") + + count = 0 + for i in G.V().has(gripql.eq("max_atmosphering_speed", 1200)): + count += 1 + if count != 3: + errors.append("Expected 3 results but got %d instead" % (count)) + + #Every vertex needs to be indexed for this "index" feature to work in grids driver + G.addIndex("Starship", "max_atmosphering_speed") + G.addIndex("Character", "max_atmosphering_speed") + G.addIndex("Planet", "max_atmosphering_speed") + G.addIndex("Species", "max_atmosphering_speed") + G.addIndex("Film", "max_atmosphering_speed") + + # Add the other index to verify that using the other execution pipline path won't change the end result + + count = 0 + for i in G.V().has(gripql.eq("max_atmosphering_speed", 1200)): + count += 1 + if count != 3: + errors.append("Expected 3 results but got %d instead" % (count)) return errors diff --git a/conformance/tests/ot_job.py b/conformance/tests/ot_job.py index 3f7e61874..fdbe2ac44 100644 --- a/conformance/tests/ot_job.py +++ b/conformance/tests/ot_job.py @@ -1,13 +1,12 @@ from __future__ import absolute_import -import gripql import time def test_job(man): errors = [] G = man.setGraph("swapi") - job = G.query().V().hasLabel("Planet").as_("a").out().submit() + job = G.V().hasLabel("Planet").as_("a").out().submit() count = 0 for j in G.listJobs(): @@ -18,7 +17,7 @@ def test_job(man): while True: cJob = G.getJob(job["id"]) - print(cJob) + #print(cJob) if cJob['state'] not in ["RUNNING", "QUEUED"]: break time.sleep(1) @@ -30,7 +29,7 @@ def test_job(man): if count != 12: errors.append("Incorrect # elements returned %d != %d" % (count, 12)) - jobs = G.query().V().hasLabel("Planet").as_("a").out().out().count().searchJobs() + jobs = G.V().hasLabel("Planet").as_("a").out().out().count().searchJobs() count = 0 for cJob in jobs: if cJob["id"] != job["id"]: @@ -41,29 +40,33 @@ def test_job(man): errors.append("Job not found in search: %d" % (count)) fullResults = [] - for res in G.query().V().hasLabel("Planet").out().out().count(): + fullCount = 0 + for res in G.V().hasLabel("Planet").out().out().count(): fullResults.append(res) + fullCount = res["count"] resumedResults = [] for res in G.resume(job["id"]).out().count().execute(): resumedResults.append(res) + if res["count"] != fullCount: + errors.append("Incorrect saved count returned: %d != %d" % (res["count"], fullCount)) if len(fullResults) != len(resumedResults): - errors.append( "Missmatch on resumed result" ) + errors.append( """Missmatch on resumed result: G.V().hasLabel("Planet").out().out().count()""" ) fullResults = [] - for res in G.query().V().hasLabel("Planet").as_("a").out().out().select("a"): + for res in G.V().hasLabel("Planet").as_("a").out().out().select("a"): fullResults.append(res) #TODO: in the future, this 'fix' may need to be removed. #Always producing elements in the same order may become a requirement. - fullResults.sort(key=lambda x:x["gid"]) + fullResults.sort(key=lambda x:x["_id"]) resumedResults = [] for res in G.resume(job["id"]).out().select("a").execute(): resumedResults.append(res) - resumedResults.sort(key=lambda x:x["gid"]) + resumedResults.sort(key=lambda x:x["_id"]) if len(fullResults) != len(resumedResults): - errors.append( "Missmatch on resumed result" ) + errors.append( """Missmatch on resumed result: G.V().hasLabel("Planet").as_("a").out().out().select("a")""" ) for a, b in zip(fullResults, resumedResults): if a != b: diff --git a/conformance/tests/ot_keycheck.py b/conformance/tests/ot_keycheck.py index c42d72612..5a24ea254 100644 --- a/conformance/tests/ot_keycheck.py +++ b/conformance/tests/ot_keycheck.py @@ -18,19 +18,19 @@ def test_subkey(man): G.addEdge("Workflow", "OtherGuy", "edge") count = 0 - for i in G.query().V("Work").out(): + for i in G.V("Work").out(): count += 1 if count != 1: errors.append("Incorrect outgoing vertex count %d != %d" % (count, 1)) count = 0 - for i in G.query().V("Work").outE(): + for i in G.V("Work").outE(): count += 1 if count != 1: errors.append("Incorrect outgoing edge count %d != %d" % (count, 1)) count = 0 - for i in G.query().V("Other").inE(): + for i in G.V("Other").inE(): count += 1 if count != 1: errors.append("Incorrect incoming edge count %d != %d" % (count, 1)) diff --git a/conformance/tests/ot_labels.py b/conformance/tests/ot_labels.py index ff802d1ac..d610c3b5a 100644 --- a/conformance/tests/ot_labels.py +++ b/conformance/tests/ot_labels.py @@ -6,7 +6,7 @@ def test_list_labels(man): G = man.setGraph("swapi") resp = G.listLabels() - print(resp) + #print(resp) if len(resp["vertexLabels"]) != 6: errors.append("listLabels returned an unexpected number of vertex labels; %d != 2" % (len(resp["vertex_labels"]))) diff --git a/conformance/tests/ot_mark.py b/conformance/tests/ot_mark.py index 18ab5c3fb..e39c1bb24 100644 --- a/conformance/tests/ot_mark.py +++ b/conformance/tests/ot_mark.py @@ -6,16 +6,16 @@ def test_mark_select_label_filter(man): G = man.setGraph("swapi") count = 0 - for row in G.query().V("Film:1").as_("a").\ + for row in G.V("Film:1").as_("a").\ both("films").\ as_("b").\ - select(["a", "b"]): + render({"a" : "$a", "b" : "$b"}): count += 1 if len(row) != 2: errors.append("Incorrect number of marks returned") - if row["a"]["gid"] != "Film:1": + if row["a"]["_id"] != "Film:1": errors.append("Incorrect vertex returned for 'a': %s" % row["a"]) - if row["b"]["label"] not in ["Vehicle", "Starship", "Species", "Planet", "Character"]: + if row["b"]["_label"] not in ["Vehicle", "Starship", "Species", "Planet", "Character"]: errors.append("Incorrect vertex returned for 'b': %s" % row["b"]) if count != 38: @@ -31,16 +31,16 @@ def test_mark_select(man): G = man.setGraph("swapi") count = 0 - for row in G.query().V("Character:1").as_("a").out().as_( - "b").out().as_("c").select(["a", "b", "c"]): + for row in G.V("Character:1").as_("a").out().as_( + "b").out().as_("c").render({"a": "$a", "b": "$b", "c": "$c"}): count += 1 if len(row) != 3: errors.append("Incorrect number of marks returned") - if row["a"]["gid"] != "Character:1": + if row["a"]["_id"] != "Character:1": errors.append("Incorrect vertex returned for 'a': %s" % row["a"]) - if row["a"]["data"]["height"] != 172: + if row["a"]["height"] != 172: errors.append("Missing data for 'a'") - if row["b"]["label"] not in ["Starship", "Planet", "Species", "Film"]: + if row["b"]["_label"] not in ["Starship", "Planet", "Species", "Film"]: errors.append("Incorrect vertex returned for 'b': %s" % row["b"]) if count != 64: @@ -56,18 +56,18 @@ def test_mark_edge_select(man): G = man.setGraph("swapi") count = 0 - for row in G.query().V("Film:1").as_("a").outE("planets").as_( - "b").out().as_("c").select(["a", "b", "c"]): + for row in G.V("Film:1").as_("a").outE("planets").as_( + "b").out().as_("c").render({"a":"$a", "b":"$b", "c":"$c"}): count += 1 if len(row) != 3: errors.append("Incorrect number of marks returned") - if row["a"]["gid"] != "Film:1": + if row["a"]["_id"] != "Film:1": errors.append("Incorrect as selection") - if row["b"]["label"] != "planets": + if row["b"]["_label"] != "planets": errors.append("Incorrect as edge selection: %s" % row["b"]) - if "scene_count" not in row["b"]["data"]: + if "scene_count" not in row["b"]: errors.append("Data not returned") - if row["c"]["label"] != "Planet": + if row["c"]["_label"] != "Planet": errors.append("Incorrect element returned") if count != 3: diff --git a/conformance/tests/ot_nested_index.py b/conformance/tests/ot_nested_index.py new file mode 100644 index 000000000..be8093ad4 --- /dev/null +++ b/conformance/tests/ot_nested_index.py @@ -0,0 +1,42 @@ +import gripql + +def test_cond_nested_field(man): + # Tests nested filters on optimized grids query driver + + errors = [] + G = man.writeTest() + + G.addIndex("Observation", "code.coding.[0].code") + + G.addVertex("1", "Observation", {"resourceType": "Observation", "id": "e87cecef-c91d-3861-a35e-6eaed41580c8", "status": "final", "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "laboratory"}]}], "code": {"coding": [{"system": "http://loinc.org", "code": "81247-9", "display": "Master HL7 genetic variant reporting panel"}]}, "subject": {"reference": "Patient/ac0d7a82-82cb-4aec-b859-e37375f3de8b"}, "specimen": {"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}, "focus": [{"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}], "effectiveDateTime": "2024-06-03T08:00:00+00:00", "valueString": "Sequencing parameters", "component": [{"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "concentration", "display": "concentration"}], "text": "concentration"}, "valueQuantity": {"value": 0.16}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_quantity", "display": "aliquot_quantity"}], "text": "aliquot_quantity"}, "valueQuantity": {"value": 2.13}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_volume", "display": "aliquot_volume"}], "text": "aliquot_volume"}, "valueQuantity": {"value": 13.3}}]}) + G.addVertex("2", "Observation", {"resourceType": "Observation", "id": "f87cecef-c91d-3861-a35e-6eaed41580c8", "status": "final", "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "laboratory"}]}], "code": {"coding": [{"system": "http://loinc.org", "code": "81247-8", "display": "Master HL7 genetic variant reporting panel"}]}, "subject": {"reference": "Patient/ac0d7a82-82cb-4aec-b859-e37375f3de8b"}, "specimen": {"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}, "focus": [{"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}], "effectiveDateTime": "2024-06-03T08:00:00+00:00", "valueString": "Sequencing parameters", "component": [{"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "concentration", "display": "concentration"}], "text": "concentration"}, "valueQuantity": {"value": 0.16}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_quantity", "display": "aliquot_quantity"}], "text": "aliquot_quantity"}, "valueQuantity": {"value": 2.13}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_volume", "display": "aliquot_volume"}], "text": "aliquot_volume"}, "valueQuantity": {"value": 13.3}}]}) + + count = 0 + for i in G.V().has(gripql.eq("code.coding.[0].code","81247-8")): + count +=1 + + if count != 1: + errors.append("Expected count == 1 but got %d instead" % (count)) + + return errors + + +def test_bulk_cond_nested_field(man): + errors = [] + G = man.writeTest() + G.addIndex("Observation", "code.coding.[0].code") + + bulk = G.bulkAdd() + bulk.addVertex("1", "Observation", {"resourceType": "Observation", "id": "e87cecef-c91d-3861-a35e-6eaed41580c8", "status": "final", "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "laboratory"}]}], "code": {"coding": [{"system": "http://loinc.org", "code": "81247-9", "display": "Master HL7 genetic variant reporting panel"}]}, "subject": {"reference": "Patient/ac0d7a82-82cb-4aec-b859-e37375f3de8b"}, "specimen": {"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}, "focus": [{"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}], "effectiveDateTime": "2024-06-03T08:00:00+00:00", "valueString": "Sequencing parameters", "component": [{"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "concentration", "display": "concentration"}], "text": "concentration"}, "valueQuantity": {"value": 0.16}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_quantity", "display": "aliquot_quantity"}], "text": "aliquot_quantity"}, "valueQuantity": {"value": 2.13}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_volume", "display": "aliquot_volume"}], "text": "aliquot_volume"}, "valueQuantity": {"value": 13.3}}]}) + bulk.addVertex("2", "Observation", {"resourceType": "Observation", "id": "f87cecef-c91d-3861-a35e-6eaed41580c8", "status": "final", "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "laboratory"}]}], "code": {"coding": [{"system": "http://loinc.org", "code": "81247-8", "display": "Master HL7 genetic variant reporting panel"}]}, "subject": {"reference": "Patient/ac0d7a82-82cb-4aec-b859-e37375f3de8b"}, "specimen": {"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}, "focus": [{"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}], "effectiveDateTime": "2024-06-03T08:00:00+00:00", "valueString": "Sequencing parameters", "component": [{"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "concentration", "display": "concentration"}], "text": "concentration"}, "valueQuantity": {"value": 0.16}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_quantity", "display": "aliquot_quantity"}], "text": "aliquot_quantity"}, "valueQuantity": {"value": 2.13}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_volume", "display": "aliquot_volume"}], "text": "aliquot_volume"}, "valueQuantity": {"value": 13.3}}]}) + + bulk.execute() + + count = 0 + for i in G.V().has(gripql.eq("code.coding.[0].code","81247-8")): + count +=1 + + if count != 1: + errors.append("Expected count == 1 but got %d instead" % (count)) + + return errors diff --git a/conformance/tests/ot_null.py b/conformance/tests/ot_null.py index 51d5fc939..60ddf8d8a 100644 --- a/conformance/tests/ot_null.py +++ b/conformance/tests/ot_null.py @@ -23,19 +23,29 @@ def test_returnNil(man): #print("query 1") count_1 = 0 - for i in G.query().V().hasLabel("Character").outNull("starships"): - print(i) + for i in G.V().hasLabel("Character").outNull("starships"): + #print(i) count_1 += 1 #print("query 1") count_1 = 0 - for i in G.query().V().hasLabel("Character").outENull("starships"): - print(i) + for i in G.V().hasLabel("Character").outENull("starships"): + #print(i) count_1 += 1 - return errors + return errors + +def test_returnNilUnwind(man): + errors = [] + G = man.setGraph("swapi") + + # outNull generates null maps that must be skipped in the unwind step. Was causing segfault before. + for i in G.V().outNull("species").unwind('eye_colors'): + if i['eye_colors'] is not None and not isinstance(i['eye_colors'], str): + errors.append("expecting i['eye_colors'] to be string after unwind but got %s instead" % i['eye_colors']) + return errors def test_hasLabelOut(man): errors = [] @@ -43,7 +53,7 @@ def test_hasLabelOut(man): #print("query 1") count_1 = 0 - for i in G.query().V().hasLabel("Character").as_("a").out("starships").as_("b").render(["$a._gid", "$b._gid", "$b._label"]): + for i in G.V().hasLabel("Character").as_("a").out("starships").as_("b").render(["$a._id", "$b._id", "$b._label"]): #print("out", i) if i[0] in noStarshipCharacters: errors.append("%s should not have been found" % (i[0])) @@ -52,7 +62,7 @@ def test_hasLabelOut(man): #print("query 2") count_2 = 0 nullFound = [] - for i in G.query().V().hasLabel("Character").as_("a").outNull("starships").as_("b").render(["$a._gid", "$b._gid", "$b._label"]): + for i in G.V().hasLabel("Character").as_("a").outNull("starships").as_("b").render(["$a._id", "$b._id", "$b._label"]): #print("outnull", i) if i[0] in noStarshipCharacters: nullFound.append(i[0]) @@ -73,7 +83,7 @@ def test_hasLabelOutE(man): #print("query 1") count_1 = 0 - for i in G.query().V().hasLabel("Character").as_("a").outE("starships").as_("b").render(["$a._gid", "$b._gid", "$b._label"]): + for i in G.V().hasLabel("Character").as_("a").outE("starships").as_("b").render(["$a._id", "$b._id", "$b._label"]): #print("out", i) if i[0] in noStarshipCharacters: errors.append("%s should not have been found" % (i[0])) @@ -82,7 +92,7 @@ def test_hasLabelOutE(man): #print("query 2") count_2 = 0 nullFound = [] - for i in G.query().V().hasLabel("Character").as_("a").outENull("starships").as_("b").render(["$a._gid", "$b._gid", "$b._label"]): + for i in G.V().hasLabel("Character").as_("a").outENull("starships").as_("b").render(["$a._id", "$b._id", "$b._label"]): #print("outnull", i) if i[0] in noStarshipCharacters: nullFound.append(i[0]) @@ -116,13 +126,13 @@ def test_hasLabelIn(man): G = man.setGraph("swapi") - for i in G.query().V().hasLabel("Character").as_("a").in_("residents").as_("b").render(["$a._gid", "$b._gid", "$b._label"]): + for i in G.V().hasLabel("Character").as_("a").in_("residents").as_("b").render(["$a._id", "$b._id", "$b._label"]): #print("in:", i) if i[0] in noResidenceCharacters: errors.append("%s should not have been found" % (i[0])) nullFound = [] - for i in G.query().V().hasLabel("Character").as_("a").inNull("residents").as_("b").render(["$a._gid", "$b._gid", "$b._label"]): + for i in G.V().hasLabel("Character").as_("a").inNull("residents").as_("b").render(["$a._id", "$b._id", "$b._label"]): #print("inNull:", i) if i[0] in noResidenceCharacters: nullFound.append(i[0]) @@ -138,13 +148,13 @@ def test_hasLabelInE(man): G = man.setGraph("swapi") - for i in G.query().V().hasLabel("Character").as_("a").inE("residents").as_("b").render(["$a._gid", "$b._gid", "$b._label"]): + for i in G.V().hasLabel("Character").as_("a").inE("residents").as_("b").render(["$a._id", "$b._id", "$b._label"]): #print("in:", i) if i[0] in noResidenceCharacters: errors.append("%s should not have been found" % (i[0])) nullFound = [] - for i in G.query().V().hasLabel("Character").as_("a").inENull("residents").as_("b").render(["$a._gid", "$b._gid", "$b._label"]): + for i in G.V().hasLabel("Character").as_("a").inENull("residents").as_("b").render(["$a._id", "$b._id", "$b._label"]): #print("inNull:", i) if i[0] in noResidenceCharacters: nullFound.append(i[0]) diff --git a/conformance/tests/ot_path.py b/conformance/tests/ot_path.py index 22261a2c0..f8fa54b56 100644 --- a/conformance/tests/ot_path.py +++ b/conformance/tests/ot_path.py @@ -8,7 +8,7 @@ def test_path_out_out_out(man): G = man.setGraph("swapi") count = 0 - for res in G.query().V("Film:1").out().out().out().path(): + for res in G.V("Film:1").out().out().out().path(): if res[0]['vertex'] != "Film:1": errors.append("Wrong first step %s != %s" % (res[0]['vertex'], "Film:1")) count += 1 @@ -22,7 +22,7 @@ def test_path_in_in(man): G = man.setGraph("swapi") count = 0 - for res in G.query().V("Film:1").in_().in_().path(): + for res in G.V("Film:1").in_().in_().path(): if res[0]['vertex'] != "Film:1": errors.append("Wrong first step %s != %s" % (res[0]['vertex'], "Film:1")) count += 1 @@ -34,7 +34,7 @@ def test_path_outE_out_select(man): errors = [] G = man.setGraph("swapi") count = 0 - for res in G.query().V("Film:1").as_("a").outE().as_("b").out().select("b").path(): + for res in G.V("Film:1").as_("a").outE().as_("b").out().select("b").path(): count += 1 if len(res) != 4: errors.append("Wrong path length %d != %d" % (4, len(res))) @@ -49,7 +49,7 @@ def test_path_out_out_select(man): errors = [] G = man.setGraph("swapi") count = 0 - for res in G.query().V("Film:1").as_("a").out().as_("b").out().select("a").path(): + for res in G.V("Film:1").as_("a").out().as_("b").out().select("a").path(): if len(res) != 4: errors.append("Wrong path length %d != %d" % (4, len(res))) else: diff --git a/conformance/tests/ot_path_optimize.py b/conformance/tests/ot_path_optimize.py index fcabd3526..78ec50f35 100644 --- a/conformance/tests/ot_path_optimize.py +++ b/conformance/tests/ot_path_optimize.py @@ -13,30 +13,30 @@ def test_path_1(man): G = man.setGraph("swapi") count = 0 - for res in G.query().V("Film:1").out().out().out(): + for res in G.V("Film:1").out().out().out(): count += 1 if count != 1814: errors.append("out-out-out Incorrect vertex count returned: %d != %d" % (count, 1814)) count = 0 - for res in G.query().V("Film:1").in_().in_().in_(): + for res in G.V("Film:1").in_().in_().in_(): count += 1 if count != 1814: errors.append("in-in-in Incorrect vertex count returned: %d != %d" % (count, 1814)) count = 0 - for res in G.query().V("Film:1").out().out().outE(): - if res["label"] not in ["vehicles", "species", "planets", "characters", "enemy", "starships", "films", "homeworld", "people", "pilots", "residents"]: - errors.append("Wrong label found at end of path: %s" % (res["label"])) + for res in G.V("Film:1").out().out().outE(): + if res["_label"] not in ["vehicles", "species", "planets", "characters", "enemy", "starships", "films", "homeworld", "people", "pilots", "residents"]: + errors.append("Wrong label found at end of path: %s" % (res["_label"])) count += 1 if count != 1814: - errors.append("out-out-outE Incorrect vertex count returned: %d != %d" % (count, 1814)) + errors.append("""V("Film:1").out().out().outE() Incorrect vertex count returned: %d != %d""" % (count, 1814)) count = 0 - for res in G.query().V("Film:1").out().out().outE().out(): + for res in G.V("Film:1").out().out().outE().out(): count += 1 if count != 1814: - errors.append("out-out-outE-out Incorrect vertex count returned: %d != %d" % (count, 1814)) + errors.append(""".V("Film:1").out().out().outE().out() Incorrect vertex count returned: %d != %d""" % (count, 1814)) return errors @@ -47,7 +47,7 @@ def test_path_2(man): G = man.setGraph("swapi") count = 0 - for res in G.query().V().out().hasLabel("Starship").out().out(): + for res in G.V().out().hasLabel("Starship").out().out(): count += 1 if count != 666: errors.append("out-hasLabel-out-out Incorrect vertex count returned: %d != %d" % (count, 666)) diff --git a/conformance/tests/ot_pivot.py b/conformance/tests/ot_pivot.py new file mode 100644 index 000000000..faa2bb198 --- /dev/null +++ b/conformance/tests/ot_pivot.py @@ -0,0 +1,22 @@ +from __future__ import absolute_import + +import gripql + + +def test_pivot(man): + errors = [] + G = man.setGraph("fhir") + + ## TODO: better result checking + count = 0 + for row in G.V().hasLabel("Patient").as_("a").out("patient_observation").pivot("$a._id", "$.key", "$.value" ): + if row["_id"] not in ["patient_a", "patient_b"]: + errors.append("Unexpected id: %s" % (row["_id"])) + count += 1 + + if count == 0: + errors.append("nothing to return") + + + + return errors diff --git a/conformance/tests/ot_render.py b/conformance/tests/ot_render.py index f01eb8a3c..16fcd75f9 100644 --- a/conformance/tests/ot_render.py +++ b/conformance/tests/ot_render.py @@ -6,34 +6,42 @@ def test_render(man): G = man.setGraph("swapi") - query = G.query().V().hasLabel("Person").render( + query = G.V().hasLabel("Character").render( { "Name": "name", "Age": "age" } ) + count = 0 for row in query: + count += 1 if 'Age' not in row or "Name" not in row: errors.append("Missing fields") - - query = G.query().V().hasLabel("Person").render( + if count != 18: + errors.append("Incorrect number of rows returned") + query = G.V().hasLabel("Character").render( { "Name": "name", "NonExistent": "non-existent" } ) + count = 0 for row in query: + count += 1 if 'NonExistent' not in row or "Name" not in row: errors.append("Missing fields") + if count != 18: + errors.append("Incorrect number of rows returned") - query = G.query().V().hasLabel("Person").render(["name", "age"]) + query = G.V().hasLabel("Character").render(["name", "age"]) for row in query: + count += 1 if not isinstance(row, list): errors.append("unexpected output format") if len(row) != 2: errors.append("Missing fields") - query = G.query().V().hasLabel("Person").render(["name", "non-existent"]) + query = G.V().hasLabel("Character").render(["name", "non-existent"]) for row in query: if not isinstance(row, list): errors.append("unexpected output format") @@ -41,3 +49,24 @@ def test_render(man): errors.append("Missing fields") return errors + + +def test_render_mark(man): + """ + test_render_mark check if various mark symbols are recalled correctly + """ + errors = [] + + G = man.setGraph("swapi") + + query = G.V().hasLabel("Character").as_("char").out("starships").render(["$char.name", "$._id", "$"]) + for row in query: + if not isinstance(row[0], str): + errors.append("incorrect return type: %s" % row[0]) + if '_id' not in row[2]: + errors.append("incorrect return type: %s" % row[2]) + if '_label' not in row[2]: + errors.append("incorrect return type: %s" % row[2]) + #print(row) + + return errors diff --git a/conformance/tests/ot_repeat.py b/conformance/tests/ot_repeat.py index 91630765a..5f96dd2c8 100644 --- a/conformance/tests/ot_repeat.py +++ b/conformance/tests/ot_repeat.py @@ -7,7 +7,7 @@ def test_repeat(man): errors = [] G = man.setGraph("swapi") - q = G.query().V("Character:1").set("count", 0).as_("start").mark("a").out().increment("$start.count") + q = G.V("Character:1").set("count", 0).as_("start").mark("a").out().increment("$start.count") q = q.has(gripql.lt("$start.count", 2)) q = q.jump("a", None, True) @@ -19,7 +19,7 @@ def test_repeat(man): errors.append("cycle output count %d != %d" % (count, 4)) #do a deeper search, to see if channels are overloaded - q = G.query().V().set("count", 0).as_("start").mark("a").increment("$start.count") + q = G.V().set("count", 0).as_("start").mark("a").increment("$start.count") q = q.has(gripql.lt("$start.count", 4)).out() q = q.jump("a", None, True) @@ -36,7 +36,7 @@ def test_forward(man): errors = [] G = man.setGraph("swapi") - q = G.query().V().jump("skip", gripql.eq( "_label", "Character" ), True).out() + q = G.V().jump("skip", gripql.eq( "_label", "Character" ), True).out() q = q.has(gripql.eq( "_label", "Character" )) q = q.mark("skip").path() @@ -61,7 +61,7 @@ def test_infinite(man): errors = [] G = man.setGraph("swapi") - q = G.query().V("Character:1").mark("a").out() + q = G.V("Character:1").mark("a").out() q = q.jump("a", None, True).limit(100) count = 0 @@ -78,21 +78,21 @@ def test_set(man): errors = [] G = man.setGraph("swapi") - q = G.query().V("Character:1").set("count", 0) - q = q.as_("start").render("$start._data") + q = G.V("Character:1").set("count", 0) + q = q.as_("start").render("$start") for row in q: if row['count'] != 0: errors.append("Incorrect increment value") - q = G.query().V("Character:1").set("count", 0).as_("start").out().increment("$start.count") - q = q.render("$start._data") + q = G.V("Character:1").set("count", 0).as_("start").out().increment("$start.count") + q = q.render("$start") for row in q: if row['count'] != 1: errors.append("Incorrect increment value") - q = G.query().V("Character:1").set("count", 0).as_("start").out().increment("$start.count") + q = G.V("Character:1").set("count", 0).as_("start").out().increment("$start.count") q = q.increment("$start.count").has(gripql.gt("$start.count", 1.0)) - q = q.render("$start._data") + q = q.render("$start") count = 0 for row in q: count += 1 @@ -101,8 +101,8 @@ def test_set(man): if count != 4: errors.append("Incorrect number of rows returned") - q = G.query().V("Character:1").set("count", 0).increment("count",2).as_("start").out().increment("$start.count") - q = q.render("$start._data") + q = G.V("Character:1").set("count", 0).increment("count",2).as_("start").out().increment("$start.count") + q = q.render("$start") for row in q: if row['count'] != 3: errors.append("Incorrect increment value") diff --git a/conformance/tests/ot_schema.py b/conformance/tests/ot_schema.py index 2493a9f0f..af89e2349 100644 --- a/conformance/tests/ot_schema.py +++ b/conformance/tests/ot_schema.py @@ -1,4 +1,5 @@ +import json def test_getscheama(man): errors = [] @@ -7,7 +8,7 @@ def test_getscheama(man): s = G.sampleSchema() - vLabels = sorted( list(v['gid'] for v in s['vertices']) ) + vLabels = sorted( list(v['id'] for v in s['vertices']) ) vExpectedLabels = [ 'Character', 'Film', 'Planet', 'Species', 'Starship', 'Vehicle' @@ -26,5 +27,25 @@ def test_getscheama(man): errors.append("Incorrect labels returned from sampling: %s != %s " % (eLabels, eExpectedLabels) ) + return errors + + +def test_post_json_schema(man): + errors = [] + G = man.setGraph("swapi") + G.addJsonSchema(load_json_schema("conformance/graphs/prompt-schema.json")) + fetched_schema = G.getSchema() + len_vertices = len(fetched_schema['vertices']) + if len_vertices != 2: + errors.append(f"incorrect number of vertices in schema {len_vertices} != 2") + len_edges = len(fetched_schema['edges']) + if len_edges != 0: + errors.append(f"incorrect number of edges in schema {len_vertices} != 0") return errors + + +def load_json_schema(path): + with open(path, 'r') as file: + content = file.read() + return json.loads(content) diff --git a/conformance/tests/ot_select.py b/conformance/tests/ot_select.py index c7cdd6721..63902d631 100644 --- a/conformance/tests/ot_select.py +++ b/conformance/tests/ot_select.py @@ -8,12 +8,12 @@ def test_simple(man): G = man.setGraph("swapi") - q = G.query().V().hasLabel("Character").as_("a").out().select("a") + q = G.V().hasLabel("Character").as_("a").out().select("a") count = 0 for row in q: count += 1 - if row["label"] != "Character": + if row["_label"] != "Character": errors.append("Wrong node label") if count != 52: errors.append("Incorrect count %d != %d" % (count, 52)) @@ -25,15 +25,15 @@ def test_select(man): G = man.setGraph("swapi") - q = G.query().V().hasLabel("Character").as_("person") + q = G.V().hasLabel("Character").as_("person") q = q.out("homeworld").has(gripql.eq("name", "Tatooine")).select("person") q = q.out("species") found = 0 for row in q: found += 1 - if row["data"]["name"] not in ["Human", "Droid"]: - errors.append("Bad connection found: %s" % (row["data"]["name"])) + if row["name"] not in ["Human", "Droid"]: + errors.append("Bad connection found: %s" % (row["name"])) if found != 7: errors.append("Incorrect number of people found: %s != 7" % (found)) diff --git a/conformance/tests/ot_sort.py b/conformance/tests/ot_sort.py new file mode 100644 index 000000000..4e081d3f4 --- /dev/null +++ b/conformance/tests/ot_sort.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import + +import gripql + +def test_sort_name(man): + errors = [] + + G = man.setGraph("swapi") + + q = G.V().hasLabel("Character").sort( "name" ) + last = "" + for row in q: + #print(row) + if row["name"] < last: + errors.append("incorrect sort: %s < %s" % (row["name"], last)) + last = row["name"] + return errors + + + +def test_sort_units(man): + errors = [] + G = man.setGraph("swapi") + + q = G.V().hasLabel("Vehicle").sort( "max_atmosphering_speed" ) + last = 0 + for row in q: + #print(row) + value = row["max_atmosphering_speed"] + if value < last: + errors.append("incorrect sort: %s < %s" % (value, last)) + last = value + + + q = G.V().hasLabel("Vehicle").sort( "max_atmosphering_speed", descending=True ) + last = 1000000000 + for row in q: + #print(row) + value = row["max_atmosphering_speed"] + if value > last: + errors.append("incorrect sort: %s > %s" % (value, last)) + last = value + + return errors diff --git a/conformance/tests/ot_struct.py b/conformance/tests/ot_struct.py index 3350786ef..22f0e9518 100644 --- a/conformance/tests/ot_struct.py +++ b/conformance/tests/ot_struct.py @@ -8,9 +8,9 @@ def test_vertex_struct(man): G.addVertex("vertex1", "person", {"field1": {"test": 1, "value": False}}) count = 0 - for i in G.query().V(): + for i in G.V(): count += 1 - p = i['data']['field1'] + p = i['field1'] if not isinstance(p, dict): errors.append("Dictionary data failed") continue @@ -36,12 +36,12 @@ def test_edge_struct(man): G.addEdge("vertex1", "vertex2", "friend", {"edgevals": {"weight": 3.14, "count": 15}}) - for i in G.query().V("vertex1").outE(): - if 'weight' not in i['data']['edgevals'] or i['data']['edgevals']['weight'] != 3.14: + for i in G.V("vertex1").outE(): + if 'weight' not in i['edgevals'] or i['edgevals']['weight'] != 3.14: errors.append("out edge data not found") - for i in G.query().V("vertex2").inE(): - if 'weight' not in i['data']['edgevals'] or i['data']['edgevals']['weight'] != 3.14: + for i in G.V("vertex2").inE(): + if 'weight' not in i['edgevals'] or i['edgevals']['weight'] != 3.14: errors.append("in edge data not found") return errors @@ -57,16 +57,16 @@ def test_nested_struct(man): G.addVertex("vertex1", "person", data) count = 0 - for i in G.query().V(): + for i in G.V(): count += 1 try: - p = i['data']["field1"]['nested']["array"][0]["value"]["entry"] + p = i["field1"]['nested']["array"][0]["value"]["entry"] if p != 1: errors.append("Incorrect values in structure") except KeyError: errors.append( "Vertex not packed correctly %s != %s" % - (data, i['data'])) + (data, i)) if count != 1: errors.append("Vertex struct property count failed") diff --git a/conformance/tests/ot_totype.py b/conformance/tests/ot_totype.py new file mode 100644 index 000000000..936072e52 --- /dev/null +++ b/conformance/tests/ot_totype.py @@ -0,0 +1,62 @@ +import gripql + +def test_totype(man): + errors = [] + + G = man.setGraph("swapi") + + q = G.V().hasLabel("Character").totype("birth_year", "string").totype("eye_color", "float").totype("hair_color", "int").totype("skin_color", "list").totype("mass", "string").execute() + if len(q) == 0: + errors.append("ERROR, q returns no items") + for row in q: + data = row + # transforms that should work + if not isinstance(data["birth_year"], str): + errors.append("string field %s should be string" % (data["birth_year"])) + if not isinstance(data["skin_color"], list): + errors.append("string field %s should be list" % (data["skin_color"])) + # some masses are none by default + if data["mass"] is not None and not isinstance(data["mass"], str): + errors.append("int field %d should be string" % (data["mass"])) + + # transforms that shouldn't work' + if data["eye_color"] != 0.0: + errors.append("string value %s should be 0.0" % (data["eye_color"])) + if data["hair_color"] != 0: + errors.append("%s should be 0" % (data["hair_color"])) + + r = G.V().hasLabel("Starship").totype("hyperdrive_rating", "string").totype("length", "int").totype("length", "float").totype("system", "list").execute() + if len(r) == 0: + errors.append("ERROR, r returns no items") + for row in r: + data = row + if isinstance(data["hyperdrive_rating"], int) or isinstance(data["hyperdrive_rating"], float): + errors.append("float field %s should be int or float" %(data["hyperdrive_rating"])) + if not isinstance(data["length"], int): + errors.append("float field %s should be int" %(data["length"])) + if not isinstance(data["system"], list): + errors.append("dict object with key 'system' %s should be list" %(data["system"])) + + s = G.V().hasLabel("Species").totype("system.created", "bool").execute() + if len(s) == 0: + errors.append("ERROR, s returns no items") + for row in s: + data = row + if data["system"]["created"] != True: + errors.append("string %s to bool should be False" %(data["system"]["created"])) + + + t = G.V().hasLabel("Starship").totype("MGLT", "bool").totype("eye_colors", "int").totype("classification", "int").execute() + if len(t) == 0: + errors.append("ERROR, t returns no items") + + for row in t: + data = row + if data["length"] is False: + errors.append("int %s to bool should be True" %(data["MGLT"])) + if data["eye_colors"] != 0: + errors.append("list %s to bool should be False" %(data["eye_colors"])) + if data["classification"] != 0: + errors.append("string %s to bool should be False" %(data["classification"])) + + return errors diff --git a/conformance/tests/ot_transform.py b/conformance/tests/ot_transform.py index f3c9fdc46..6c8da071d 100644 --- a/conformance/tests/ot_transform.py +++ b/conformance/tests/ot_transform.py @@ -1,4 +1,5 @@ +from re import I import gripql def test_count(man): @@ -6,8 +7,9 @@ def test_count(man): G = man.setGraph("swapi") - q = G.query().V().hasLabel("Planet").unwind("terrain").aggregate(gripql.term("t", "terrain")) + q = G.V().hasLabel("Planet").unwind("terrain").aggregate(gripql.term("t", "terrain")) count = 0 + for row in q: if row['key'] not in ['rainforests', 'desert', 'mountains', 'jungle', 'rainforests', 'grasslands']: errors.append("Incorrect value %s returned" % row['key']) @@ -19,3 +21,49 @@ def test_count(man): errors.append("Incorrect # elements returned") return errors + + +def test_unwind(man): + errors = [] + G = man.writeTest() + bulk = G.bulkAdd() + bulk.addVertex("1", "Observation", {"resourceType": "Observation", "id": "e87cecef-c91d-3861-a35e-6eaed41580c8", "status": "final", "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "laboratory"}]}], "code": {"coding": [{"system": "http://loinc.org", "code": "81247-9", "display": "Master HL7 genetic variant reporting panel"}]}, "subject": {"reference": "Patient/ac0d7a82-82cb-4aec-b859-e37375f3de8b"}, "specimen": {"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}, "focus": [{"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}], "effectiveDateTime": "2024-06-03T08:00:00+00:00", "valueString": "Sequencing parameters", "component": [{"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "concentration", "display": "concentration"}], "text": "concentration"}, "valueQuantity": {"value": 0.16}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_quantity", "display": "aliquot_quantity"}], "text": "aliquot_quantity"}, "valueQuantity": {"value": 2.13}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_volume", "display": "aliquot_volume"}], "text": "aliquot_volume"}, "valueQuantity": {"value": 13.3}}]}) + err = bulk.execute() + + q = G.V().hasLabel("Observation").unwind("component") + count = 0 + for row in q: + #print("ROW: ", row) + count +=1 + if count != 3: + errors.append("There should be 3 vertices after unwind process") + + q = G.V().hasLabel("Observation").unwind("component").has(gripql.gt("component.valueQuantity.value", 1)) + count = 0 + for r in q: + count +=1 + if count != 2: + errors.append("There should be 2 vertices after unwind process and filter") + + return errors + + +def test_unwind_group_totype(man): + errors = [] + G = man.writeTest() + bulk = G.bulkAdd() + bulk.addVertex("1", "Observation", {"resourceType": "Observation", "id": "e87cecef-c91d-3861-a35e-6eaed41580c8", "status": "final", "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "laboratory"}]}], "code": {"coding": [{"system": "http://loinc.org", "code": "81247-9", "display": "Master HL7 genetic variant reporting panel"}]}, "subject": {"reference": "Patient/ac0d7a82-82cb-4aec-b859-e37375f3de8b"}, "specimen": {"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}, "focus": [{"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}], "effectiveDateTime": "2024-06-03T08:00:00+00:00", "valueString": "Sequencing parameters", "component": [{"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "concentration", "display": "concentration"}], "text": "concentration"}, "valueQuantity": {"value": 0.16}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_quantity", "display": "aliquot_quantity"}], "text": "aliquot_quantity"}, "valueQuantity": {"value": 2.13}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation", "code": "aliquot_volume", "display": "aliquot_volume"}], "text": "aliquot_volume"}, "valueQuantity": {"value": 13.3}}]}) + + bulk.addVertex("2", "Specimen", {"resourceType": "Specimen", "id": "dc48f578-193c-4740-93f3-61a78e3c6ba0", "status": "final", "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "laboratory"}]}], "code": {"coding": [{"system": "http://loinc.org", "code": "81247-9", "display": "Master HL7 genetic variant reporting panel"}]}, "subject": {"reference": "Patient/ac0d7a82-82cb-4aec-b859-e37375f3de8b"}, "specimen": {"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}, "focus": [{"reference": "Specimen/dc48f578-193c-4740-93f3-61a78e3c6ba0"}], "effectiveDateTime": "2024-06-03T08:00:00+00:00", "valueString": "Sequencing parameters", "component": [{"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation1", "code": "concentration", "display": "concentration"}], "text": "concentration"}, "valueQuantity": {"value": 0.16}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation2", "code": "aliquot_quantity", "display": "aliquot_quantity"}], "text": "aliquot_quantity"}, "valueQuantity": {"value": 2.13}}, {"code": {"coding": [{"system": "https://cadsr.cancer.gov/sample_laboratory_observation3", "code": "aliquot_volume", "display": "aliquot_volume"}], "text": "aliquot_volume"}, "valueQuantity": {"value": 13.3}}]}) + bulk.addEdge("1", "2", "focus_Specimen") + err = bulk.execute() + + orig_row = {} + for i in G.V().hasLabel("Observation").as_("f0").out("focus_Specimen"): + orig_row = i + + for i in G.V().hasLabel("Observation").as_("f0").out("focus_Specimen").unwind("component").unwind("component.code.coding").as_("f1").totype("$f1.component.code.coding","list").group({"component":"$f1.component"}): + if not isinstance(i["component"][0]["code"]["coding"], list) and isinstance(orig_row["component"][0]["code"]["coding"], list): + errors.append("Original row list format not preserved: %s !=\n\n %s" % (orig_row, i)) + + return errors diff --git a/conformance/tests/ot_update.py b/conformance/tests/ot_update.py index 8d0ac9972..d7d8e03c5 100644 --- a/conformance/tests/ot_update.py +++ b/conformance/tests/ot_update.py @@ -1,3 +1,7 @@ + + + + def test_duplicate(man): G = man.writeTest() @@ -9,14 +13,14 @@ def test_duplicate(man): G.addVertex("vertex2", "person") G.addVertex("vertex2", "clone") - G.addEdge("vertex1", "vertex2", "friend", data={"field": 1}, gid="edge1") - G.addEdge("vertex1", "vertex2", "friend", gid="edge1") - G.addEdge("vertex1", "vertex2", "friend", data={"weight": 5}, gid="edge1") + G.addEdge("vertex1", "vertex2", "friend", data={"field": 1}, id="edge1") + G.addEdge("vertex1", "vertex2", "friend", id="edge1") + G.addEdge("vertex1", "vertex2", "friend", data={"weight": 5}, id="edge1") - if G.query().V().count().execute()[0]["count"] != 2: + if G.V().count().execute()[0]["count"] != 2: errors.append("duplicate vertex add error") - if G.query().E().count().execute()[0]["count"] != 1: + if G.V().outE().count().execute()[0]["count"] != 1: errors.append("duplicate edge add error") return errors @@ -33,18 +37,19 @@ def test_replace(man): G.addVertex("vertex2", "person") G.addVertex("vertex2", "clone") - G.addEdge("vertex1", "vertex2", "friend", data={"field": 1}, gid="edge1") - G.addEdge("vertex1", "vertex2", "friend", gid="edge1") - G.addEdge("vertex1", "vertex2", "friend", data={"weight": 5}, gid="edge1") + G.addEdge("vertex1", "vertex2", "friend", data={"field": 1}, id="edge1") + G.addEdge("vertex1", "vertex2", "friend", id="edge1") + G.addEdge("vertex1", "vertex2", "friend", data={"weight": 5}, id="edge1") - if G.getVertex("vertex1")["label"] != "clone": + if G.getVertex("vertex1")["_label"] != "clone": errors.append("vertex has unexpected label") - if G.getVertex("vertex1")["data"] != {"otherdata": "foo"}: - errors.append("vertex has unexpected data") + # TODO: Fix these + if G.getVertex("vertex1") != {"_id":"vertex1", "_label" : "clone", "otherdata": "foo"}: + errors.append("vertex has unexpected data: %s" % (G.getVertexw("vertex1"))) - if G.getEdge("edge1")["data"] != {"weight": 5}: - errors.append("edge is missing expected data") + if G.getEdge("edge1")["weight"] != 5: + errors.append("edge is missing expected data: %s" % (G.getEdge("edge1"))) return errors @@ -59,49 +64,48 @@ def test_delete(man): G.addVertex("vertex3", "person", {"field1": "value3", "field2": "value4"}) G.addVertex("vertex4", "person") - G.addEdge("vertex1", "vertex2", "friend", gid="edge1") - G.addEdge("vertex2", "vertex3", "friend", gid="edge2") - G.addEdge("vertex2", "vertex4", "parent", gid="edge3") + G.addEdge("vertex1", "vertex2", "friend", id="edge1") + G.addEdge("vertex2", "vertex3", "friend", id="edge2") + G.addEdge("vertex2", "vertex4", "parent", id="edge3") count = 0 - for i in G.query().V(): + for i in G.V(): count += 1 if count != 4: - errors.append("Fail: G.query().V() %s != %s" % (count, 4)) + errors.append("Fail: G.V() %s != %s" % (count, 4)) count = 0 - for i in G.query().E(): + for i in G.V().outE(): count += 1 if count != 3: - errors.append("Fail: G.query().E()") + errors.append("Fail: G.V().outE()") G.deleteVertex("vertex1") count = 0 - for i in G.query().V(): + for i in G.V(): count += 1 if count != 3: errors.append( - "Fail: G.query().V() %s != %d" % (count, 3)) + "Fail: G.V() %s != %d" % (count, 3)) count = 0 - for i in G.query().E(): + for i in G.V().outE(): count += 1 if count != 2: errors.append( - "Fail: G.query().E() %s != %d" % (count, 2)) + "Fail: G.V().outE() %s != %d" % (count, 2)) G.deleteEdge("edge2") count = 0 - for i in G.query().E(): + for i in G.V().outE(): count += 1 if count != 1: errors.append( - "Fail: G.query().E() %s != %d" % (count, 1)) + "Fail: G.V().outE() %s != %d" % (count, 1)) return errors - def test_delete_edge(man): """ Ensure that if a vertex is removed, that the connected edges are deleted as well @@ -114,35 +118,35 @@ def test_delete_edge(man): G.addVertex("vertex2", "person") G.addVertex("vertex3", "person", {"field1": "value3", "field2": "value4"}) - G.addEdge("vertex1", "vertex2", "friend", gid="edge1") - G.addEdge("vertex2", "vertex3", "friend", gid="edge2") + G.addEdge("vertex1", "vertex2", "friend", id="edge1") + G.addEdge("vertex2", "vertex3", "friend", id="edge2") count = 0 - for i in G.query().V(): + for i in G.V(): count += 1 if count != 3: - errors.append("Fail: G.query().V() %s != %s" % (count, 3)) + errors.append("Fail: G.V() %s != %s" % (count, 3)) count = 0 - for i in G.query().E(): + for i in G.V().outE(): count += 1 if count != 2: - errors.append("Fail: G.query().E()") + errors.append("Fail: G.V().outE()") G.deleteVertex("vertex2") count = 0 - for i in G.query().V("vertex1").outE(): + for i in G.V("vertex1").outE(): count += 1 if count != 0: errors.append( - "Fail: G.query().V(\"vertex1\").outE() %s != %d" % (count, 0)) + "Fail: G.V(\"vertex1\").outE() %s != %d" % (count, 0)) count = 0 - for i in G.query().V("vertex3").inE(): + for i in G.V("vertex3").inE(): count += 1 if count != 0: errors.append( - "Fail: G.query().V(\"vertex3\").inE() %s != %d" % (count, 0)) + "Fail: G.V(\"vertex3\").inE() %s != %d" % (count, 0)) return errors diff --git a/conformance/tests/real.py b/conformance/tests/real.py new file mode 100644 index 000000000..d200ce81a --- /dev/null +++ b/conformance/tests/real.py @@ -0,0 +1,37 @@ +import json + +def load_json_schema(path): + with open(path, 'r') as file: + content = file.read() + return json.loads(content) + + +def test_bulk_add_raw(man): + errors = [] + + G = man.writeTest() + G.addJsonSchema(load_json_schema("schema.json")) + + with open("Observation.ndjson", "r") as fread: + count = 0 + bulkRaw = G.bulkAddRaw() # Start initial stream + + for line in fread: + bulkRaw.addJson(data=json.loads(line)) + count += 1 + if count % 100000 == 0: + print(f"read {count} lines") + err = bulkRaw.execute() # Execute and close current stream + print("ERR: ", err) + if len(err["errors"]) > 0: + errors.extend(err["errors"]) + bulkRaw = G.bulkAddRaw() # Start new stream for next batch + + # Handle remaining items + if count % 100000 != 0: + err = bulkRaw.execute() # Final execution + print("Final ERR: ", err) + if len(err["errors"]) > 0: + errors.extend(err["errors"]) + + return errors diff --git a/docs/categories/index.xml b/docs/categories/index.xml index bb6d02fdd..8f7de3938 100644 --- a/docs/categories/index.xml +++ b/docs/categories/index.xml @@ -4,11 +4,8 @@ Categories on GRIP https://bmeg.github.io/grip/categories/ Recent content in Categories on GRIP - Hugo -- gohugo.io + Hugo en-us - - - - + - \ No newline at end of file + diff --git a/docs/docs/clients/index.html b/docs/docs/clients/index.html new file mode 100644 index 000000000..9fb48744c --- /dev/null +++ b/docs/docs/clients/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + Client Library · GRIP + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+

Getting Started

+

GRIP has an API for making graph queries using structured data. Queries are defined using a series of step operations.

+

Install the Python Client

+

Available on PyPI.

+
pip install gripql
+

Or install the latest development version:

+
pip install "git+https://github.com/bmeg/grip.git#subdirectory=gripql/python"
+

Using the Python Client

+

Let’s go through the features currently supported in the python client.

+

First, import the client and create a connection to an GRIP server:

+
import gripql
+G = gripql.Connection("https://bmeg.io").graph("bmeg")
+

Some GRIP servers may require authorizaiton to access its API endpoints. The client can be configured to pass +authorization headers in its requests.

+
import gripql
+
+# Basic Auth Header - {'Authorization': 'Basic dGVzdDpwYXNzd29yZA=='}
+G = gripql.Connection("https://bmeg.io", user="test", password="password").graph("bmeg")
+#
+
+# Bearer Token - {'Authorization': 'Bearer iamnotarealtoken'}
+G = gripql.Connection("https://bmeg.io", token="iamnotarealtoken").graph("bmeg")
+
+# OAuth2 / Custom - {"OauthEmail": "fake.user@gmail.com", "OauthAccessToken": "iamnotarealtoken", "OauthExpires": 1551985931}
+G = gripql.Connection("https://bmeg.io",  credential_file="~/.grip_token.json").graph("bmeg")
+

Now that we have a connection to a graph instance, we can use this to make all of our queries.

+

One of the first things you probably want to do is find some vertex out of all of the vertexes available in the system. In order to do this, we need to know something about the vertex we are looking for. To start, let’s see if we can find a specific gene:

+
result = G.V().hasLabel("Gene").has(gripql.eq("symbol", "TP53")).execute()
+print(result)
+

A couple things about this first and simplest query. We start with O, our grip client instance connected to the “bmeg” graph, and create a new query with ``. This query is now being constructed. You can chain along as many operations as you want, and nothing will actually get sent to the server until you print the results.

+

Once we make this query, we get a result:

+
[
+  {
+    u'_id': u'ENSG00000141510',
+    u'_label': u'Gene'
+    u'end': 7687550,
+    u'description': u'tumor protein p53 [Source:HGNC Symbol%3BAcc:HGNC:11998]',
+    u'symbol': u'TP53',
+    u'start': 7661779,
+    u'seqId': u'17',
+    u'strand': u'-',
+    u'id': u'ENSG00000141510',
+    u'chromosome': u'17'
+  }
+]
+

This represents the vertex we queried for above. All vertexes in the system will have a similar structure, basically:

+
    +
  • _id: This represents the global identifier for this vertex. In order to draw edges between different vertexes from different data sets we need an identifier that can be constructed from available data. Often, the _id will be the field that you query on as a starting point for a traversal.
  • +
  • _label: The label represents the type of the vertex. All vertexes with a given label will share many property keys and edge labels, and form a logical group within the system.
  • +
+

The data on a query result can be accessed as properties on the result object; for example result[0].data.symbol would return:

+
u'TP53'
+

You can also do a has query with a list of items using gripql.within([...]) (other conditions exist, see the Conditions section below):

+
result = G.V().hasLabel("Gene").has(gripql.within("symbol", ["TP53", "BRCA1"])).render({"_id": "_id", "symbol":"symbol"}).execute()
+print(result)
+

This returns both Gene vertexes:

+
[
+  {u'symbol': u'TP53', u'_id': u'ENSG00000141510'},
+  {u'symbol': u'BRCA1', u'_id': u'ENSG00000012048'}
+]
+

Once you are on a vertex, you can travel through that vertex’s edges to find the vertexes it is connected to. Sometimes you don’t even need to go all the way to the next vertex, the information on the edge between them may be sufficient.

+

Edges in the graph are directional, so there are both incoming and outgoing edges from each vertex, leading to other vertexes in the graph. Edges also have a label, which distinguishes the kind of connections different vertexes can have with one another.

+

Starting with gene TP53, and see what kind of other vertexes it is connected to.

+
result = G.V().hasLabel("Gene").has(gripql.eq("symbol", "TP53")).in_("TranscriptFor")render({"id": "_id", "label":"_label"}).execute()
+print(result)
+

Here we have introduced a couple of new steps. The first is .in_(). This starts from wherever you are in the graph at the moment and travels out along all the incoming edges. +Additionally, we have provided TranscriptFor as an argument to .in_(). This limits the returned vertices to only those connected to the Gene verticies by edges labeled TranscriptFor.

+
[
+  {u'_label': u'Transcript', u'_id': u'ENST00000413465'},
+  {u'_label': u'Transcript', u'_id': u'ENST00000604348'},
+  ...
+]
+

View a list of all available query operations here.

+

Using the command line

+

Grip command line syntax is defined at gripql/javascript/gripql.js

+ +
+ +
+ + diff --git a/docs/docs/databases/elastic/index.html b/docs/docs/commands/create/index.html similarity index 80% rename from docs/docs/databases/elastic/index.html rename to docs/docs/commands/create/index.html index 4badd322b..61ac9951a 100644 --- a/docs/docs/databases/elastic/index.html +++ b/docs/docs/commands/create/index.html @@ -8,7 +8,7 @@ - Elasticsearch · GRIP + create · GRIP @@ -32,7 +32,7 @@ diff --git a/docs/graph_schemas/index.html b/docs/docs/commands/delete/index.html similarity index 51% rename from docs/graph_schemas/index.html rename to docs/docs/commands/delete/index.html index 31784dc99..d4dddfccd 100644 --- a/docs/graph_schemas/index.html +++ b/docs/docs/commands/delete/index.html @@ -8,7 +8,7 @@ - Graph Schemas · GRIP + delete · GRIP @@ -32,7 +32,7 @@ diff --git a/docs/docs/commands/drop/index.html b/docs/docs/commands/drop/index.html index 5401498c4..b625b766d 100644 --- a/docs/docs/commands/drop/index.html +++ b/docs/docs/commands/drop/index.html @@ -32,7 +32,7 @@