(This page is a work in progress)
GitHub Repos:
“I’ve been a developer for over 20 years and I’ve worked with a lot of different ORMs and the thing that bothers me most about them is that I already know how to talk to a database. In other words, I know SQL very well… I tried ditching ORMs in the past and when I did a funny thing happened. I eventually found myself kind of reimplementing an ORM and that’s when I came to realize that you can’t really escape ORMs. You either use an existing one or you end up building your own. So I think that ORMs are pretty much unavoidable. So the question I am left with is why can’t ORMs be a little bit better and a little less frustrating to somebody like myself - a SQL fan.” (The ORM I don’t hate (Drizzle))
One of the pros of relational databases is that SQL is a standardized query language. Well, if SQL is often abstracted away by ORMs, then is the query language still standardized?
GQL does a very nice job of representing the data that is being queried and it is a pleasure to work with. There is no need to abstract it away with something like an ORM.
These steps follow the instructions on these pages:
When I talk about GQL I am talking about ISO-GQL, not GraphQL. (Maybe I should explain the difference between GQL and GraphQL.)
MATCH
queries that are supposed to return multiple nodes only return the PRIMARY_ID of those nodes as opposed to the attributes of those nodes. That is pretty significant!What is a cluster? See What Is A Server Cluster?
NOTE: If you are coming back to tgcloud.io and logging in after you have previously created a free cluster, then you will need start your free cluster again:
IMPORTANT NOTE: As you create your schema, make sure to save your changes regularly by clicking the Save schema
button in the toolbar.
These naming conventions are listed here as a reference. Skip to the next step and refer back to this section as you create the different parts of your schema.
While I am using openCypher (until GQL is fully implemented in TigerGraph), I will use the naming conventions from the Cypher Style Guide.
The graph names should use capitalized snake case.
Example: My_Graph_Dev
, My_Graph_Prod
The node names should be Pascal case, or upper camel case (i.e. the first letter of each word is capitalized). I think node names should also be singular.
Example: UserAccount
Each relationship/edge should describe the relationship and should use all caps snake case (i.e. all the letters are capitalized and words are separated by underscores). The name of the relationship can simply be a combination of the starting vertex and the target vertex (e.g. STARTING_VERTEX_NAME_TARGET_VERTEX_NAME
).
Example: USER_ACCOUNT_USER
Important Note: One question that needs to be answered is what properties should be stored in relationships vs nodes? This post on StackOverflow helps to answer that question: https://stackoverflow.com/questions/28121589/neo4j-storing-into-relationship-vs-nodes. It might be more useful to store properties in nodes and keep relationships simple. i.e. Just use relationship names that describe the relationship and do not store any properties in the relationships. It might be fine to include a createdAt
property in the relationship, but it might also be better to store that in the nodes.
All property names should be camel case.
Example: userId
The only way to run openCypher queries against a TigerGraph database is through “installed queries,” which you can create and save in GraphStudio.
Query names should use snake case.
Example: get_user_by_id
Source: Write openCypher Queries in TigerGraph
In the graph database world, a graph is a database. So when we say something like, “Create a new graph”, it means to create a new database.
Read CREATE GRAPH for more details about this step.
Refer to the naming conventions above.
Add local vertex type
button (the plus circle
icon) in the toolbar.Add local edge type
button (the arrow
icon) in the toolbar then choose the starting vertex type and the target vertex type.Edit
button (the pencil
icon) in the toolbar.Delete
button (the trash can
icon) in toolbar.Publish
button (the fat up arrow
icon) in toolbar. (TODO: Figure out why I don’t see this button.)You can apply any or all vertex and edge types to your other graphs:
.env
file in the root of your project and add a variable for your TigerGraph secret (e.g. TG_SECRET
). .env
files for each environment (e.g. .env.development
, .env.test
, .env.staging
, .env.production
) and define the same environment variable in each file (e.g. TG_SECRET
) but assign the corresponding secret for the given environment. If your framework is configured to load different .env
files for different environments, then the correct value will be loaded when you start your app..env
file (e.g. TG_DEV_SECRET
, TG_TEST_SECRET
, TG_STAGING_SECRET
, TG_PROD_SECRET
). You can then use if
statements to check the environment and assign the corresponding secret values to your app code.wrangler.toml
file for environment variables that you want to check into source control and a .dev.vars
file for environment variables that you do NOT want to check in to source control. .dev.vars
file are only accessible in your local development environment.TIGERGRAPH_SECRET
..env
or .dev.vars
file and add any necessary environment variables to your web host’s environment variables storage.If you ever need to delete a secret and/or create a new one, then you can do that on the above page. Make sure to update it in each location that it is being used.
NOTE: The JWT expires after 12 hours. So I need to ask about two things in the support forums:
.env
file, create an environment variable for your TG Cloud JWT: TG_JWT = ""
/statistics/{graph_name}
query.TG_JWT
environment variable.Creat some other environment variables next to your TIGERGRAPH_SECRET
environment variable:
TIGERGRAPH_DOMAIN = ""
TIGERGRAPH_GRAPH_NAME = ""
TIGERGRAPH_DOMAIN
valuei.tgcloud.io
.TIGERGRAPH_DOMAIN
variable.NOTE: If you are using PyTigerGraph, then the domain
variable will be labelled host
.
TIGERGRAPH_GRAPH_NAME
valueTODO: CONTINUE HERE: Finish this tutorial with a demo about creating your own language driver using JavaScript. Look at the pyTigerGraph docs and GitHub repo for ideas. Look at my api-fetch-request/index.ts
file in the client side code for ideas on how to write and organize some of the driver code.
The response that is returned from the /requesttoken
endpoint has this payload:
{
"code": "REST-0000",
"expiration": 1616042814,
"error": false,
"message": "Generate new token successfully.\nWarning: Tigergraph Support cannot restore access to secrets/tokens for security reasons. Please save your secret/token and keep it safe and accessible.",
"token": "tohvf6khjqju8jf0r0l1cohhlm8gi5fq"
}
The token has a 1 month expiration by default. You could rotate tokens more frequently (e.g. every hour) by setting a shorter expiration (e.g. 1 hour from now) and then checking the token’s expiration with each request to your database. If the token is expired, or if it is close to expiring, then send a request for a new token.
We will use openCypher as our query language as much as possible (until GQL is fully implemented into TigerGraph). At the time of this writing, the only way to run openCypher queries against a TigerGraph database is through “installed queries.”
IMPORTANT: I found out that large parts of openCypher are not supported in TigerGraph. See https://docs.tigergraph.com/gsql-ref/4.1/opencypher-in-gsql/opencypher-in-gsql. So I will have to learn GSQL for the parts that are not supported and then I can use GQL/openCypher for the parts that are supported.
This is how an openCypher query in GraphStudio should look:
CREATE OR REPLACE DISTRIBUTED OPENCYPHER QUERY create_user_account_and_user(
STRING firstName,
STRING lastName,
STRING email,
JSONARRAY permissions
) FOR GRAPH My_App_Dev {
CREATE (ua:UserAccount {
permissions: permissions
})
CREATE (u:User {
firstName: firstName,
lastName: lastName,
email: email
})
MERGE (ua)-[r:ACCOUNT_OWNER]->(u)
RETURN ua, u
}
NOTE: I am pretty sure that the LIST<STRING> permissions
param is correctly declared, but it throws errors in the GraphStudio UI. See https://docs.tigergraph.com/gsql-ref/4.1/ddl-and-loading/attribute-data-types#_collection_types
runInstalledQuery()
inside your API endpoint to run the query against the TigerGraph database.
These CRUD operations use installed queries.
Create queries are not available in GQL/openCypher yet. So this query will use GSQL.
For more details, see INSERT INTO Statement.
CREATE OR REPLACE DISTRIBUTED QUERY create_user_account_and_user_gsql(
STRING userAccountId,
STRING userId,
STRING firstName,
STRING lastName,
STRING email,
STRING permissions
) FOR GRAPH My_App_Dev {
// Installed queries do not support JSONARRAY types as parameters yet. So in your installed queries you have to set any array parameters as a STRING data type,
// then you have to JSON.stringify() any arrays before passing them to the installed query, then you have to parse that STRING (array) parameter with
// parse_json_array() before you can use it as an array in your installed query.
// https://docs.tigergraph.com/gsql-ref/4.1/querying/data-types#_jsonarray
// JSONARRAY parsedPermissions = parse_json_array(permissions);
INSERT INTO UserAccount (PRIMARY_ID, permissions)
VALUES (userAccountId, permissions)
;
INSERT INTO User (PRIMARY_ID, firstName, lastName, email)
VALUES (userId, firstName, lastName, email)
;
INSERT INTO ACCOUNT_OWNER (FROM, TO)
VALUES (userAccountId, userId)
;
STRING success_message = "User and user account created successfully!";
PRINT success_message;
}
The installed query will return a data object like this:
{
version: { edition: 'enterprise', api: 'v2', schema: 3 },
error: false,
message: '',
results: [
{ key1: value1 },
{ key2: value2 },
{ key3: value3 }
]
}
Each PRINT
statement in the installed query will add another object to the results
array field of the data object that is returned from the installed query.
NOTES:
runInterpretedQuery()
. However, runInterpretedQuery()
might not work with GQL/openCypher queries because these requests go directly to the GSQL server instead of the RESTPP server.runInstalledQuery()
because it allows us to test the query against our database first (similar to how you use Postman to test APIs before hooking up a frontend client) and then we can simply reference the query in our code when it is ready.CONTINUE HERE:
Watch this video to understand pyTigerGraph (continue at about 23:00): https://docs.tigergraph.com/pytigergraph/current/getting-started/101.
Reference this page: https://docs.tigergraph.com/pytigergraph/current/core-functions/
This video helped to understand web development with TigerGraph: 101 Full-Stack App with TigerGraph
Reference this page: https://docs.tigergraph.com/tigergraph-server/current/api/built-in-endpoints.
TODO: Add this tutorial to my samuelearl.com website. Change the name of one of my YouTube channels to “Sam Not-So-Wise Gamgee” and record a video. I could start like this: “I have had a lot of people ask me, “Not-So-Wise, who do I get started with graph databases? [Record scratching] Actually, most people don’t even know what a graph database is. They think it is something related to GraphQL…”
NOTE: Maybe I will call my YouTube channel something that suggests that I am not an expert or that I am a little behind the times or slow.
.env
file, create variables for TIGERGRAPH_HOST
and TIGERGRAPH_GRAPH_NAME
..env
file and import them like this:import pyTigerGraph as tg
from dotenv import load_dotenv
load_dotenv()
TG_DEV_HOST = os.getenv("TG_DEV_HOST")
TG_DEV_GRAPH_NAME = os.getenv("TG_DEV_GRAPH_NAME")
TG_DEV_SECRET = os.getenv("TG_DEV_SECRET")
tg_host = ""
tg_graph_name = ""
tg_secret = ""
if LITESTAR_ENV == "development":
tg_host = TG_DEV_HOST
tg_graph_name = TG_DEV_GRAPH_NAME
tg_secret = TG_DEV_SECRET
db = tg.TigerGraphConnection(host=tg_host, graphname=tg_graph_name, gsqlSecret=tg_secret)
db.getToken(tg_secret)
This way you can list your .env
file in your .gitignore
file, which will prevent you from checking sensitive information into your Git repo.
# /database/main.py
...
try:
db = tg.TigerGraphConnection(host=tg_host, graphname=tg_graph_name, gsqlSecret=tg_secret)
db.getToken(tg_secret)
version = db.getVer()
print(f"TigerGraph version {version} has been initialized!")
return
except:
log_error()
In order to use the database (connection) object in an endpoint, you will import the database object at the top of your contorller file, like this:
from database.main import db
The database connection will be initialized when your app starts and you can reuse the connection in your other endpoints.