{"id":13722648,"url":"https://github.com/threatgrid/asami","last_synced_at":"2026-01-26T18:19:58.049Z","repository":{"id":31764660,"uuid":"128827980","full_name":"threatgrid/asami","owner":"threatgrid","description":"A graph store for Clojure and ClojureScript","archived":false,"fork":false,"pushed_at":"2022-03-30T19:13:38.000Z","size":34706,"stargazers_count":637,"open_issues_count":68,"forks_count":29,"subscribers_count":23,"default_branch":"main","last_synced_at":"2024-11-14T12:50:12.730Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/threatgrid.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-09T20:10:47.000Z","updated_at":"2024-11-02T04:00:43.000Z","dependencies_parsed_at":"2022-08-07T16:31:00.144Z","dependency_job_id":null,"html_url":"https://github.com/threatgrid/asami","commit_stats":null,"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threatgrid%2Fasami","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threatgrid%2Fasami/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threatgrid%2Fasami/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threatgrid%2Fasami/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/threatgrid","download_url":"https://codeload.github.com/threatgrid/asami/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252915204,"owners_count":21824520,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T01:01:31.265Z","updated_at":"2026-01-26T18:19:57.991Z","avatar_url":"https://github.com/threatgrid.png","language":"Clojure","funding_links":[],"categories":["数据库","Clojure"],"sub_categories":[],"readme":"\u003e This repository is no longer being maintained. For ongoing development, please see: https://github.com/quoll/asami.\n\n# asami [![Build Status](https://travis-ci.org/threatgrid/asami.svg?branch=main)](https://travis-ci.org/threatgrid/asami) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md)\n\nA graph database, for Clojure and ClojureScript.\n\nThe latest version is :\n\n[![Clojars Project](http://clojars.org/org.clojars.quoll/asami/latest-version.svg)](http://clojars.org/org.clojars.quoll/asami)\n\n## Goals\nAsami is both similar to and different from other graph databases. Some of the goals of the project are:\n- **Schema-less data**. Data can be loaded without prior knowledge of its structures.\n- **Stable**. Storage uses immutable structures to ensure that writes cannot lead to data corruption.\n- **Multiplatform**. Asami runs on the Java Virtual Machine and on JavaScript platforms (browsers, node.js, etc).\n- **Ease of setup**. Asami managed storage requires no provisioning, and can be created in a single statement.\n- **Plugable**. Storage is a pluggable system that allows for multiple storage types, both local and remote.\n- **Analytics**. Graph analytics are provided by using internal mechanisms for efficiency.\n\nAsami is a _schemaless_ database, meaning that data may be inserted with no predefined schema. This flexibility has advantages and disadvantages. It is easier to load and evolve data over time without a schema. However, functionality like upsert and basic integrity checking is not available in the same way as with a graph with a predefined schema. Optional schemas are on the roadmap to help with this.\n\nAsami also follows an _Open World Assumption_ model, in the same way that [RDF](http://www.w3.org/TR/rdf-primer) does. In practice, this has very little effect on the database, beyond what being schemaless provides.\n\nIf you are new to graph databases, then please read our [Introduction page](https://github.com/threatgrid/asami/wiki/2.-Introduction).\n\nAsami has a query API that looks very similar to a simplified Datomic. More details are available in the [Query documentation](https://github.com/threatgrid/asami/wiki/6.-Querying).\n\n## Features\nThere are several other graph databases available in the Clojure ecosystem, with each having their own focus. Asami is characterized by the following:\n- Clojure and ClojureScript: Asami runs identically in both systems.\n- Schema-less: Asami does not require a schema to insert data.\n- Query planner: Queries are analyzed to find an efficient execution plan. This can be turned off.\n- Analytics: Supports fast graph traversal operations, such as transitive closures, and can identify subgraphs.\n- Integrated with Loom: Asami graphs are valid Loom graphs, via [Asami-Loom](https://github.com/threatgrid/asami-loom).\n- Open World Assumption: Related to being schema-less, Asami borrows semantics from [RDF](http://www.w3.org/TR/rdf-primer) to lean towards an open world model.\n- Pluggable Storage: Like Datomic, storage in Asami can be implemented in multiple ways. There are currently 2 in-memory graph systems, and durable storage available on the JVM.\n\n## Usage\n### Installing\nUsing Asami requires [Clojure](https://clojure.org/guides/getting_started) or [ClojureScript](https://clojurescript.org/guides/quick-start).\n\nAsami can be made available to clojure by adding the following to a `deps.edn` file:\n```clojure\n{\n  :deps {\n    org.clojars.quoll/asami {:mvn/version \"2.2.4\"}\n  }\n}\n```\n\nThis makes Asami available to a repl that is launched with the `clj` or `clojure` commands.\n\nAlternatively, Asami can be added for the Leiningen build tool by adding this to the `:dependencies` section of the `project.clj` file:\n```clojure\n[org.clojars.quoll/asami \"2.2.4\"]\n```\n\n### Important Note for databases before 2.1.0\nAsami 2.1.0 now uses fewer files to manage data. This makes it incompatible with previous versions. To port data from an older store to a new one, use the `asami.core/export-data` function on a database on the previous version of Asami, and `asami.core/import-data` to load the data into a new connection.\n\n### Running\nThe [Asami API](https://github.com/threatgrid/asami/wiki/7.-Asami-API) tries to look a little like Datomic.\n\nOnce a repl has been configured for Asami, the following can be copy/pasted to test the API:\n```clojure\n(require '[asami.core :as d])\n\n;; Create an in-memory database, named dbname\n(def db-uri \"asami:mem://dbname\")\n(d/create-database db-uri)\n\n;; Create a connection to the database\n(def conn (d/connect db-uri))\n\n;; Data can be loaded into a database either as objects, or \"add\" statements:\n(def first-movies [{:movie/title \"Explorers\"\n                    :movie/genre \"adventure/comedy/family\"\n                    :movie/release-year 1985}\n                   {:movie/title \"Demolition Man\"\n                    :movie/genre \"action/sci-fi/thriller\"\n                    :movie/release-year 1993}\n                   {:movie/title \"Johnny Mnemonic\"\n                    :movie/genre \"cyber-punk/action\"\n                    :movie/release-year 1995}\n                   {:movie/title \"Toy Story\"\n                    :movie/genre \"animation/adventure\"\n                    :movie/release-year 1995}])\n\n@(d/transact conn {:tx-data first-movies})\n```\nThe [`transact`](https://github.com/threatgrid/asami/wiki/7.-Asami-API#transact) operation returns an object that can be _dereferenced_ (via `clojure.core/deref` or the `@` macro) to provide information about the state of the database before and after the transaction. (A _future_ in Clojure, or a _delay_ in ClojureScript). Note that the transaction data can be provided as the `:tx-data` in a map object if other parameters are to be provided, or just as a raw sequence without the wrapping map.\n\nFor more information about loading data and executing `transact` see the [Transactions documentation](https://github.com/threatgrid/asami/wiki/4.-Transactions).\n\nWith the data loaded, a database value can be retrieved from the database and then queried.\n\n**NB:** The `transact` operation will be executed asynchronously on the JVM. Retrieving a database immediately after executing a `transact` will not retrieve the latest database. If the updated database is needed, then perform the `deref` operation as shown above, since this will wait until the operation is complete.\n\n```clojure\n(def db (d/db conn))\n\n(d/q '[:find ?movie-title\n       :where [?m :movie/title ?movie-title]] db)\n```\nThis returns a sequence of results, with each result being a sequence of the selected vars in the `:find` clause (just `?movie-title` in this case):\n```\n([\"Explorers\"]\n [\"Demolition Man\"]\n [\"Johnny Mnemonic\"]\n [\"Toy Story\"])\n```\nA more complex query could be to get the title, year and genre for all movies after 1990:\n```clojure\n(d/q '[:find ?title ?year ?genre\n       :where [?m :movie/title ?title]\n              [?m :movie/release-year ?year]\n              [?m :movie/genre ?genre]\n              [(\u003e ?year 1990)]] db)\n```\nEntities found in a query can be extracted back out as objects using the [`entity`](https://github.com/threatgrid/asami/wiki/7.-Asami-API#entity) function. For instance, the following is a repl session that looks up the movies released in 1995, and then gets the associated entities:\n```clojure\n;; find the entity IDs. This variation in the :find clause asks for a list of just the ?m variable\n=\u003e (d/q '[:find [?m ...] :where [?m :movie/release-year 1995]] db)\n(:tg/node-10327 :tg/node-10326)\n\n;; get a single entity\n=\u003e (d/entity db :tg/node-10327)\n#:movie{:title \"Toy Story\",\n        :genre \"animation/adventure\",\n        :release-year 1995}\n\n;; get all the entities from the query\n=\u003e (map #(d/entity db %)\n        (d/q '[:find [?m ...] :where [?m :movie/release-year 1995]] db))\n(#:movie{:title \"Toy Story\",\n         :genre \"animation/adventure\",\n         :release-year 1995}\n #:movie{:title \"Johnny Mnemonic\",\n         :genre \"cyber-punk/action\",\n         :release-year 1995})\n```\nSee the [Query Documentation](https://github.com/threatgrid/asami/wiki/6.-Querying) for more information on querying.\n\nRefer to the [Entity Structure documentation](https://github.com/threatgrid/asami/wiki/5.-Entity-Structure) to understand how entities are stored and how to construct queries for them.\n\n### Local Storage\nThe above code uses an in-memory database, specified with a URL of the form `asami:mem://dbname`. Creating a database on disk is done the same way, but with the URL scheme changed to `asami:local://dbname`. This would create a database in the `dbname` directory. Local databases do not use keywords as entity IDs, as keywords use up memory, and a local database could be gigabytes in size. Instead, these are `InternalNode` objects. These can be created with `asami.graph/new-node`, or by using the readers in `asami.graph`. For instance, if the above code were all done with a local graph instead of a memory graph:\n```clojure\n=\u003e (d/q '[:find [?m ...] :where [?m :movie/release-year 1995]] db)\n(#a/n \"3\" #a/n \"4\")\n\n;; get a single entity\n=\u003e (require '[asami.graph :as graph])\n=\u003e (d/entity db (graph/new-node 4))\n#:movie{:title \"Toy Story\", :genre \"animation/adventure/comedy\", :release-year 1995}\n\n;; nodes can also be read from a string, with the appropriate reader\n=\u003e (set! *data-readers* graph/node-reader)\n=\u003e (d/entity db #a/n \"4\")\n#:movie{:title \"Toy Story\", :genre \"animation/adventure/comedy\", :release-year 1995}\n```\n\n### Updates\nThe _Open World Assumption_ allows each attribute to be multi-arity. In a _Closed World_ database an object may be loaded to replace those attributes that can only appear once. To do the same thing with Asami, annotate the attributes to be replaced with a quote character at the end of the attribute name. \n```clojure\n=\u003e (def toy-story (d/q '[:find ?ts . :where [?ts :movie/title \"Toy Story\"]] db))\n=\u003e (d/transact conn [{:db/id toy-story :movie/genre' \"animation/adventure/comedy\"}])\n=\u003e (d/entity (d/db conn) toy-story)\n#:movie{:title \"Toy Story\",\n        :genre \"animation/adventure/comedy\",\n        :release-year 1995}\n```\nAddressing nodes by their internal ID can be cumbersome. They can also be addressed by a `:db/ident` field if one is provided.\n```clojure\n(def tx (d/transact conn [{:db/ident \"sense\"\n                           :movie/title \"Sense and Sensibility\"\n                           :movie/genre \"drama/romance\"\n                           :movie/release-year 1996}]))\n\n;; ask the transaction for the node ID, instead of querying\n(def sense (get (:tempids @tx) \"sense\"))\n(d/entity (d/db conn) sense)\n```\nThis returns the new movie. The `:db/ident` attribute does not appear in the entity:\n```clojure\n#:movie{:title \"Sense and Sensibility\", :genre \"drama/romance\", :release-year 1996}\n```\nHowever, all of the attributes are still present in the graph:\n```clojure\n=\u003e (d/q '[:find ?a ?v :in $ ?s :where [?s ?a ?v]] (d/db conn) sense)\n([:db/ident \"sense\"] [:movie/title \"Sense and Sensibility\"] [:movie/genre \"drama/romance\"] [:movie/release-year 1996])\n```\nThe release year of this movie is incorrectly set to the release in the USA, and not the initial release. That can be updated using the `:db/ident` field:\n```clojure\n=\u003e (d/transact conn [{:db/ident \"sense\" :movie/release-year' 1995}])\n=\u003e (d/entity (d/db conn) sense)\n#:movie{:title \"Sense and Sensibility\", :genre \"drama/romance\", :release-year 1995}\n```\nMore details are provided in [Entity Updates](https://github.com/threatgrid/asami/wiki/4.-Transactions#entity-updates).\n\n## Analytics\nAsami also has some support for graph analytics. These all operate on the _graph_ part of a database value, which can be retrieved with the `asami.core/graph` function.\n\n**NB:** `local` graphs on disk are not yet supported. These will be available soon.\n\nStart by populating a graph with the cast of [\"The Flintstones\"](https://www.imdb.com/title/tt0053502/). So that we can refer to entities after they have been created, we can provide them with temporary ID values. These are just negative numbers, and can be used elsewhere in the transaction to refer to the same entity. We will also avoid the `:tx-data` wrapper in the transaction:\n```clojure\n(require '[asami.core :as d])\n(require '[asami.analytics :as aa])\n\n(def db-uri \"asami:mem://data\")\n(d/create-database db-uri)\n(def conn (d/connect db-uri))\n\n(def data [{:db/id -1 :name \"Fred\"}\n           {:db/id -2 :name \"Wilma\"}\n           {:db/id -3 :name \"Pebbles\"}\n           {:db/id -4 :name \"Dino\" :species \"Dinosaur\"}\n           {:db/id -5 :name \"Barney\"}\n           {:db/id -6 :name \"Betty\"}\n           {:db/id -7 :name \"Bamm-Bamm\"}\n           [:db/add -1 :spouse -2]\n           [:db/add -2 :spouse -1]\n           [:db/add -1 :child -3]\n           [:db/add -2 :child -3]\n           [:db/add -1 :pet -4]\n           [:db/add -5 :spouse -6]\n           [:db/add -6 :spouse -5]\n           [:db/add -5 :child -7]\n           [:db/add -6 :child -7]])\n\n(d/transact conn data)\n```\nFred, Wilma, Pebbles, and Dino are all connected in a subgraph. Barney, Betty and Bamm-Bamm are connected in a separate subgraph.\n\nLet's find the subgraph from Fred:\n```clojure\n(def db (d/db conn))\n(def graph (d/graph db))\n(def fred (d/q '[:find ?e . :where [?e :name \"Fred\"]] db))\n\n(aa/subgraph-from-node graph fred)\n```\nThis returns the _nodes_ in the graph, but not the scalar values. For instance:\n```clojure\n#{:tg/node-10330 :tg/node-10329 :tg/node-10331 :tg/node-10332}\n```\nThese nodes can be used as the input to a query to get their names:\n```clojure\n=\u003e (d/q '[:find [?name ...] :in $ [?n ...] :where [?n :name ?name]]\n        db\n        (aa/subgraph-from-node graph fred))\n(\"Fred\" \"Pebbles\" \"Dino\" \"Wilma\")\n```\n\nWe can also get all the subgraphs:\n```clojure\n=\u003e (count (aa/subgraphs graph))\n2\n\n;; execute the same query for each subgraph\n=\u003e (map (partial d/q '[:find [?name ...] :where [?e :name ?name]])\n        (aa/subgraphs graph))\n((\"Fred\" \"Wilma\" \"Pebbles\" \"Dino\") (\"Barney\" \"Betty\" \"Bamm-Bamm\"))\n```\n\n#### Transitive Queries\nAsami supports transitive properties in queries. A property (or attribute) is treated as transitive if it is followed by a `+` or a `*` character.\n```clojure\n(d/q '[:find ?friend-of-a-friend\n       :where [?person :name \"Fred\"]\n              [?person :friend+ ?foaf]\n              [?foaf :name ?friend-of-a-friend]]\n     db)\n```\nThis will find all friends, and friends of friends for Fred.\n\n### Loom\nAsami also implements [Loom](https://github.com/aysylu/loom) via the [Asami-Loom](https://github.com/threatgrid/asami-loom) package.\nInclude the following dependency for your project:\n```\n[org.clojars.quoll/asami-loom \"0.2.0\"]\n```\n\nGraphs can now be analyzed with Loom functions.\n\nIf functions are provided to Loom, then they can be used to provide labels for creating a visual graph. The following creates some simple queries to get the labels for edges and nodes:\n```clojure\n(require '[asami-loom.index])\n(require '[asami-loom.label])\n(require '[loom.io])\n\n(defn edge-label [g s d]\n  (str (d/q '[:find ?e . :in $ ?a ?b :where (or [?a ?e ?b] [?b ?e ?a])] g s d)))\n  \n(defn node-label [g n]\n  (or (d/q '[:find ?name . :where [?n :name ?name]] g n) \"-\"))\n\n;; create a PDF of the graph\n(loom-io/view (graph db) :fmt :pdg :alg :sfpd :edge-label edge-label :node-label node-label)\n```\n\n## Command Line Tool\nA command line tool is available to load data into an Asami graph and query it. This requires [GraalVM CE 21.1.0](https://www.graalvm.org/) or later, and the [`native-image`](https://www.graalvm.org/reference-manual/native-image/#install-native-image) executable.\nLeiningen needs to see GraalVM on the classpath first, so if there are problems with building, check to see if this is the case.\n\nTo build from sources:\n\n```bash\nlein with-profile native uberjar\nlein with-profile native native\n```\n\nThis will create a binary called `asami` in the `target` directory. Execute with the `-?` flag for help:\n\n```\n$ ./target/asami -?\nUsage: asami URL [-f filename] [-e query] [--help | -?]\n\n-? | --help: This help\nURL: the URL of the database to use. Must start with asami:mem://, asami:multi:// or asami:local://\n-f filename: loads the filename into the database. A filename of \"-\" will use stdin.\n             Data defaults to EDN. Filenames ending in .json are treated as JSON.\n-e query: executes a query. \"-\" (the default) will read from stdin instead of a command line argument.\n          Multiple queries can be specified as edn (vector of query vectors) or ; separated.\n\nAvailable EDN readers:\n  internal nodes -  #a/n \"node-id\"\n  regex          -  #a/r \"[Tt]his is a (regex|regular expression)\"\n```\n\n#### Example:\nLoading a json file, and querying for keys (attributes) that are strings with spaces in them:\n\n```bash\nasami asami:mem://tmp -f data.json -e ':find ?a :where [?e ?a ?v][(string? ?a)][(re-find #a/r \" \" ?a)]'\n```\n\nThe command will also work on `local` stores, which means that they can be loaded once and then queried multiple times.\n\n## License\n\nCopyright © 2016-2021 Cisco Systems\nCopyright © 2015-2022 Paula Gearon\n\nPortions of src/asami/cache.cljc are Copyright © Rich Hickey\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthreatgrid%2Fasami","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthreatgrid%2Fasami","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthreatgrid%2Fasami/lists"}