diff --git a/404.html b/404.html index d2ec17577..abad35dc0 100644 --- a/404.html +++ b/404.html @@ -5,14 +5,14 @@ Page Not Found | Glean - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/_src/angle/guide.md b/_src/angle/guide.md index ba87923fc..c215e886b 100644 --- a/_src/angle/guide.md +++ b/_src/angle/guide.md @@ -478,6 +478,41 @@ facts> X where [_,X, ..] = [1,2,3] { "id": 1040, "key": 2 } ``` +## Sets + +Sets are similar to arrays but helpful when the order of the elements are not important and duplicates are also irrelevant. +A common example is when storing cross references. For instance, the python schema has a predicate which contains all +name cross references in a file. The cross references are currently stored in an array but it could be stored in a set as below. + +```lang=angle +predicate XRefsViaNameByFile: + { + file: src.File, + xrefs: set XRefViaName, + } +``` + +If we want to know for a particular file and a particular name, where it is used we could write the following query: + +```lang=angle +XRefsViaNameByFile { file = "foo.py", xrefs = XRefs }; +{ target = { name = "Bar" } } = elements XRefs +``` + +The second line uses the construct `elements` which is similar to the `[..]` syntax for arrays. + +We can also create new sets from the results of a query. This is done using the `all` construct. For instance +`all (1 | 2 | 3)` is a set containing the number `1`, `2`, and `3`. + +The `all` construct can be used in combination with the `elements` construct to, for instance, map over a set +of elements and transform them. In the example below, the second line takes each element of the `StringSet` and +applies the primitive `prim.toLower` to it. The result is a set where all the strings are lowercase. + +```lang=angle +StringSet = all ("Foo" | "Bar" | "Baz" ); +all (String = elements StringSet; prim.toLower String) +``` + ## String prefix We’ve seen many examples of patterns that match strings. Glean also supports matching strings by *prefix*; for example: diff --git a/_src/angle/reference.md b/_src/angle/reference.md index 020795014..40bc62699 100644 --- a/_src/angle/reference.md +++ b/_src/angle/reference.md @@ -91,6 +91,14 @@ Terms have the following forms: > Note: variables mentioned in **term₁** and **term₂** are local to those terms, and may have different types, but only if the variable is not mentioned elsewhere. +  `elements` *term* + +> All the elements of the set **term** + +  `all` *query* + +> Construct a set of all the results of **query**. +   `!` *term* > The negation of a term. Fails if the term matches anything and succeeds otherwise. @@ -172,3 +180,16 @@ Angle supports a few primitive operations. The argument(s) to a primitive operat   *term* `!=` *term* > Standard comparison between two terms of any type. It has a value of `{}` if the comparison succeeds, otherwise it fails in the same way as a predicate match fails if there are no facts that match the pattern. + +  `zip` (A : [a]) (B : [b]) : [{a,b}] + +> Takes two arrays and zips them together pairwise into a new array of tuples. +If the arrays have different length, the result has the same length as the shorter input array. + +  `concat` (A : [a]) (B : [a]) : [a] + +> Concatenates two arrays together + +  `reverse` (S : string) : string + +> Reverses a string diff --git a/_src/schema/changing.md b/_src/schema/changing.md index 7ca2bd690..dcd6c392b 100644 --- a/_src/schema/changing.md +++ b/_src/schema/changing.md @@ -16,6 +16,7 @@ only be changed in **compatible** ways. This means: * Adding or removing a field from a record, if the field has a **defaultable** type (see [Default values](#default-values)) * Adding or removing an alternative from a sum type * Adding or removing a predicate or type declaration +* Changing a list to a set or vice versa An example of an *incompatible* change would be changing the type of a field, for example from `nat` to `bool`. @@ -61,6 +62,7 @@ following table: | byte | 0 | | string | "" | | [T] | [] | +| set T | the empty set | | { field₁ : T₁, ..., fieldₙ : Tₙ } | { field₁ = default(T₁), ..., fieldₙ = default(Tₙ) } | | { field₁ : T₁ | ... | fieldₙ : Tₙ } | { field₁ = default(T₁) } | | bool | false | diff --git a/_src/schema/design.md b/_src/schema/design.md index e03b2dc16..ac505499b 100644 --- a/_src/schema/design.md +++ b/_src/schema/design.md @@ -106,35 +106,35 @@ There are several tradeoffs here: * **Incrementality** * These two alternatives are equivalent with respect to incrementality. -## Using arrays +## Using arrays, sets or separate facts If you're choosing between arrays and separate facts, then consider: * Arrays are ordered lists, whereas facts are just sets. If the order of your items is important - because you're representing something that has an order, such as function arguments - then an array is the - right choice. (someday Glean might have a "set" type, but it - currently doesn't). + right choice. -* Conversely, if the order is *not* important, then using an array is +* Conversely, if the order is *not* important, then sets are the natural + choice. Using an array is a poor choice because you will be forced to choose an order when generating your data. If you don't have a deterministic way to pick the order, then your data representation is non-deterministic which leads to spurious differences in things like test outputs, which can be annoying. -* Arrays are much more compact than multiple facts. There can be a +* Arrays and sets are much more compact than multiple facts. There can be a huge difference in storage overhead; it's worth measuring this for your schema. -* When a client fetches an array as part of the result of a query, - they will get the whole array. If your array is large, that may be a +* When a client fetches an array or a set as part of the result of a query, + they will get the whole array/set. If it is large, that may be a lot of data to send over the wire, and it might even result in an allocation limit error on the server, preventing the client from fetching the data at all. Facts tend to support incremental querying - better compared with arrays. + better compared with arrays and sets. -* Facts with large arrays are also slower to search through in a query +* Facts with large arrays/sets are also slower to search through in a query than smaller facts. ## Increase sharing @@ -143,6 +143,9 @@ If there is duplication in the data stored in our facts, we can often extract the common data into a predicate to increase sharing. One example of this was described in [What is the difference between a predicate and a type?](schema/syntax.md#what-is-the-difference-between-a-predicate-and-a-type). +Choosing to use sets instead of arrays can increase sharing because sets have +a canonical representation. + ## How to experiment with schema design * Generate some data and see how large it is, using `:stat` in the shell. diff --git a/_src/schema/thrift.md b/_src/schema/thrift.md index 29eed6d7c..d605ee85a 100644 --- a/_src/schema/thrift.md +++ b/_src/schema/thrift.md @@ -37,6 +37,7 @@ The relationship between schema types and Thrift/JSON is given by the following | `bool` | `bool` | `true` or `false` | | `[byte]` | `binary` | base-64 encoded string *1 | | `[T]` | `list` | [...] | +| `set T` | `list` | [...] | |`{`
  `f₁ : T₁,`
  `...,`
  `fₙ : Tₙ`
`}` | `struct Foo {`
  `1: T₁ f₁;`
  `...`
  `n: Tₙ fₙ;`
`}` | `{`
  `"f₁" : q₁,`
  `...`
  `"fₙ" : qₙ`
`}` | | `{`
  `f₁ : T₁ `|
  `... `|
  `fₙ : Tₙ`
`}` | `union Foo {`
  `1: T₁ f₁;`
  `...`
  `n: Tₙ fₙ;`
`}` | `{ "f" : t }`
for one of the fields `f₁`..`fₙ` | | `maybe T` | In a record field:
`optional T f` | `f : t`
if the value is present | diff --git a/_src/schema/types.md b/_src/schema/types.md index 6f2bfd456..8435838ea 100644 --- a/_src/schema/types.md +++ b/_src/schema/types.md @@ -10,6 +10,7 @@ sidebar_label: Built-in Types | byte | 8-bit natural numbers | | string | UTF-8 encoded strings | | [T] | lists of elements of type T | +| set T | set of elements of type T | | { field₁ : T₁, ..., fieldₙ : Tₙ } | a record with zero or more named fields | | { field₁ : T₁ | ... | fieldₙ : Tₙ } | a sum (union) type with one or more named alternatives | | P | a reference to a fact of predicate P | diff --git a/assets/js/21418ead.51b22fa5.js b/assets/js/21418ead.51b22fa5.js new file mode 100644 index 000000000..89d383c51 --- /dev/null +++ b/assets/js/21418ead.51b22fa5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[515],{15680:(e,n,a)=>{a.r(n),a.d(n,{MDXContext:()=>s,MDXProvider:()=>h,mdx:()=>x,useMDXComponents:()=>c,withMDXComponents:()=>m});var t=a(96540);function i(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function l(){return l=Object.assign||function(e){for(var n=1;n=0||(i[a]=e[a]);return i}(e,n);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var s=t.createContext({}),m=function(e){return function(n){var a=c(n.components);return t.createElement(e,l({},n,{components:a}))}},c=function(e){var n=t.useContext(s),a=n;return e&&(a="function"==typeof e?e(n):o(o({},n),e)),a},h=function(e){var n=c(e.components);return t.createElement(s.Provider,{value:n},e.children)},p="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},f=t.forwardRef((function(e,n){var a=e.components,i=e.mdxType,l=e.originalType,r=e.parentName,s=d(e,["components","mdxType","originalType","parentName"]),m=c(a),h=i,p=m["".concat(r,".").concat(h)]||m[h]||u[h]||l;return a?t.createElement(p,o(o({ref:n},s),{},{components:a})):t.createElement(p,o({ref:n},s))}));function x(e,n){var a=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var l=a.length,r=new Array(l);r[0]=f;var o={};for(var d in n)hasOwnProperty.call(n,d)&&(o[d]=n[d]);o.originalType=e,o[p]="string"==typeof e?e:i,r[1]=o;for(var s=2;s{a.r(n),a.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var t=a(58168),i=(a(96540),a(15680));const l={id:"changing",title:"How do I change a schema?",sidebar_label:"Changing a schema"},r=void 0,o={unversionedId:"schema/changing",id:"schema/changing",title:"How do I change a schema?",description:"Glean supports modifying the schema directly, while providing",source:"@site/docs/schema/changing.md",sourceDirName:"schema",slug:"/schema/changing",permalink:"/docs/schema/changing",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/schema/changing.md",tags:[],version:"current",frontMatter:{id:"changing",title:"How do I change a schema?",sidebar_label:"Changing a schema"},sidebar:"someSidebar",previous:{title:"Recursion",permalink:"/docs/schema/recursion"},next:{title:'The special "all" schema',permalink:"/docs/schema/all"}},d={},s=[{value:"Basic rules",id:"basic-rules",level:3},{value:"Compatibility",id:"compatibility",level:3},{value:"Default values",id:"default-values",level:3},{value:"What if my schema changes are incompatible?",id:"what-if-my-schema-changes-are-incompatible",level:3},{value:"How does it work?",id:"how-does-it-work",level:3},{value:"Evolving schemas",id:"evolving-schemas",level:3}],m=(c="FbInternalOnly",function(e){return console.warn("Component "+c+" was not imported, exported, or provided by MDXProvider as global scope"),(0,i.mdx)("div",e)});var c;const h={toc:s},p="wrapper";function u(e){let{components:n,...a}=e;return(0,i.mdx)(p,(0,t.A)({},h,a,{components:n,mdxType:"MDXLayout"}),(0,i.mdx)("p",null,"Glean supports modifying the schema directly, while providing\nbackwards compatibility between existing clients and data across the\nschema change."),(0,i.mdx)("h3",{id:"basic-rules"},"Basic rules"),(0,i.mdx)("p",null,"To preserve compatibility between clients and data, the schema can\nonly be changed in ",(0,i.mdx)("strong",{parentName:"p"},"compatible")," ways. This means:"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"Adding or removing a field from a record, if the field has a ",(0,i.mdx)("strong",{parentName:"li"},"defaultable")," type (see ",(0,i.mdx)("a",{parentName:"li",href:"#default-values"},"Default values"),")"),(0,i.mdx)("li",{parentName:"ul"},"Adding or removing an alternative from a sum type"),(0,i.mdx)("li",{parentName:"ul"},"Adding or removing a predicate or type declaration"),(0,i.mdx)("li",{parentName:"ul"},"Changing a list to a set or vice versa")),(0,i.mdx)("p",null,"An example of an ",(0,i.mdx)("em",{parentName:"p"},"incompatible")," change would be changing the type of a\nfield, for example from ",(0,i.mdx)("inlineCode",{parentName:"p"},"nat")," to ",(0,i.mdx)("inlineCode",{parentName:"p"},"bool"),"."),(0,i.mdx)("p",null,"Of course it's fine to make arbitrary changes to a schema that you're\nworking on; the compatibility rules only come into effect when the\nschema is landed. From that point there might be existing clients and\ndatabases in use, so Glean will reject any incompatible schema changes."),(0,i.mdx)(m,{mdxType:"FbInternalOnly"},"The compatibility check shows up in CI as `sync-cf-main-diff` and is performed on any diff that changes a schema. It checks that the new schema is compatible with the existing schema and all previous versions of the schema still in use, so it will catch things like removing a field and then re-adding it with a different type."),(0,i.mdx)("h3",{id:"compatibility"},"Compatibility"),(0,i.mdx)("p",null,"When the schema is changed, Glean supports any combination of old or\nnew clients (that is, clients built against the old or new version of\nthe schema) with old or new data."),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"If the client requests a field that doesn't exist in the data, the field will be filled with its default value."),(0,i.mdx)("li",{parentName:"ul"},"If a query matches on an alternative that doesn't exist in the schema, the match will fail."),(0,i.mdx)("li",{parentName:"ul"},"If the data contains an alternative that doesn't exist in the client's schema, then the client will receive an ",(0,i.mdx)("inlineCode",{parentName:"li"},"unknown")," value (this is represented as an ",(0,i.mdx)("inlineCode",{parentName:"li"},"EMPTY")," constructor in the Thrift-generated datatypes).")),(0,i.mdx)("h3",{id:"default-values"},"Default values"),(0,i.mdx)("p",null,"A ",(0,i.mdx)("strong",{parentName:"p"},"defaultable")," type is any type that is not a predicate. So for\nexample, if ",(0,i.mdx)("inlineCode",{parentName:"p"},"P")," is a predicate type, then we could add a field ",(0,i.mdx)("inlineCode",{parentName:"p"},"f :\nmaybe P")," but not a field ",(0,i.mdx)("inlineCode",{parentName:"p"},"f : P"),". The reason for this restriction is\nthat Glean must be able to substitute a default value for the\nfield when a client is using the new schema but the data is using the\nold schema, and there cannot be a default value for a predicate."),(0,i.mdx)("p",null,"Default values for missing fields are determined according to the\nfollowing table:"),(0,i.mdx)("table",null,(0,i.mdx)("thead",{parentName:"table"},(0,i.mdx)("tr",{parentName:"thead"},(0,i.mdx)("th",{parentName:"tr",align:null},"Type"),(0,i.mdx)("th",{parentName:"tr",align:null},"Default value"))),(0,i.mdx)("tbody",{parentName:"table"},(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"nat")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"0"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"byte")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"0"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"string")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,'""'))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"[T]")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"[]"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null," set T")),(0,i.mdx)("td",{parentName:"tr",align:null},"the empty set")),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"{ field\u2081 : T\u2081, ..., field\u2099 : T\u2099 }")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"{ field\u2081 = default(T\u2081), ..., field\u2099 = default(T\u2099) }"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"{ field\u2081 : T\u2081 ","|"," ... ","|"," field\u2099 : T\u2099 }")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"{ field\u2081 = default(T\u2081) }"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"bool")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"false"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"maybe T")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"nothing"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"enum { name\u2081 ","|"," ... ","|"," name\u2099 }")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"name\u2081"))))),(0,i.mdx)("h3",{id:"what-if-my-schema-changes-are-incompatible"},"What if my schema changes are incompatible?"),(0,i.mdx)("p",null,"If you don't care about backwards compatibility, then an easy\nworkaround is to just bump the version of the whole schema, so for\nexample if you have"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema graphql.1 {\n...\n}\n")),(0,i.mdx)("p",null,"then change it to"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema graphql.2 {\n...\n}\n")),(0,i.mdx)("p",null,"and make whatever changes you need. The new version of the schema is\nentirely separate from the old version as far as Glean is concerned,\nso there are no restrictions on what changes can be made."),(0,i.mdx)("p",null,"If you want to retain some backwards compatibility, then you can add\nnew predicates representing the new data. You have the option of"),(0,i.mdx)("ol",null,(0,i.mdx)("li",{parentName:"ol"},"Writing both the old and the new data to the database"),(0,i.mdx)("li",{parentName:"ol"},"Producing two databases, one with the old data and one with the new data. The databases can be distinguished by different names or different properties.")),(0,i.mdx)("p",null,"With approach (1), you can migrate clients incrementally and then eventually\nstop producing the old data. But the downside of this approach is that\nthe database may contain a lot more data than necessary; writing may\ntake a lot longer, and so on."),(0,i.mdx)("p",null,"With approach (2), you first have to ensure clients select the old\ndatabase. Then as you migrate clients, change them to use the new data\nand select the new database at the same time."),(0,i.mdx)("h3",{id:"how-does-it-work"},"How does it work?"),(0,i.mdx)("p",null,"A particular instance of the schema is identified by a\n",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId"),". This is a hash value computed from the full contents of\nthe schema. The ",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId")," of the current schema is available through\nthe ",(0,i.mdx)("inlineCode",{parentName:"p"},"schema_id")," value exported by the ",(0,i.mdx)("inlineCode",{parentName:"p"},"builtin")," schema (the\n",(0,i.mdx)("inlineCode",{parentName:"p"},"Glean.Schema.Builtin.Types")," module in Haskell)."),(0,i.mdx)("p",null,"The server keeps track of multiple instances of the schema in a\n",(0,i.mdx)("strong",{parentName:"p"},"schema index"),". Each instance of the schema is identified by its\n",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId"),". Each database also contains the schema that it was written\nwith. The schema index is manipulated using the ",(0,i.mdx)("inlineCode",{parentName:"p"},"gen-schema")," tool in\nthe Glean repository; to use a particular index we can use ",(0,i.mdx)("inlineCode",{parentName:"p"},"--schema\nindexfile:FILE")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"--schema indexconfig:CONFIG"),"."),(0,i.mdx)("p",null,"A client sends its ",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId")," to the server in the ",(0,i.mdx)("inlineCode",{parentName:"p"},"schema_id")," field\nof the query. For Haskell clients this is done automatically: the\nclient passes its ",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId")," when initialising the Glean client\nlibrary, and this ",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId")," is passed on to the server for every\nquery."),(0,i.mdx)("p",null,"The server knows which schema the client is using, so it can translate\nthe data in the database into the client's schema automatically."),(0,i.mdx)("p",null,"When a database is created, the schema used by the database is chosen\nby the ",(0,i.mdx)("inlineCode",{parentName:"p"},"glean.schema_id")," property set by the client creating the\ndatabase. Again for Haskell clients this happens automatically."),(0,i.mdx)("h3",{id:"evolving-schemas"},"Evolving schemas"),(0,i.mdx)("p",null,"Glean also allows backwards-compatibility between co-existing schemas,\nwhich can be useful if you want to perform schema changes in a more\nexplicit way, or to rename schemas."),(0,i.mdx)("p",null,"The feature is enabled using a top-level directive"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema my_schema.2 evolves my_schema.1\n")),(0,i.mdx)("p",null,"This declaration has the effect of treating queries for ",(0,i.mdx)("inlineCode",{parentName:"p"},"my_schema.1")," predicates as if they were for ",(0,i.mdx)("inlineCode",{parentName:"p"},"my_schema.2"),". That is the query results will be retrieved from the database in the shape of a ",(0,i.mdx)("inlineCode",{parentName:"p"},"my_schema.2")," fact and transformed into a fact of the equivalent ",(0,i.mdx)("inlineCode",{parentName:"p"},"my_schema.1")," predicate specified in the query."),(0,i.mdx)("p",null,"The new schema must contain all the predicates of the old schema, either with new versions or old versions, and their definitions must be backwards compatible. We can achieve this by copying the entire content of the old schema into the new one and modifying it there."),(0,i.mdx)("p",null,"Now what should Glean do when a client asks for a fact from an old schema?"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"Answer with db facts from the old schema"),(0,i.mdx)("li",{parentName:"ul"},"Answer with db facts from the new schema transformed into the old ones.")),(0,i.mdx)("p",null,"If there are no facts of the old schema in in the database we will take option 2.\nIf the database has any fact at all of the old schema we choose option 1."),(0,i.mdx)("p",null,"That is, schema evolutions only take effect if there are no facts of the old schema in the database; it is ignored otherwise."),(0,i.mdx)("p",null,"As an example suppose we start with the following schemas:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'schema src.1 {\n predicate File {\n path : string\n }\n}\n\nschema os.1 {\n import src.1\n\n predicate Permissions {\n file : File,\n permissions : nat\n }\n}\n\nschema info.1 {\n import src.1\n\n predicate IsTemporary {\n file : File\n } F where F = src.File { path = "/tmp".. }\n}\n')),(0,i.mdx)("p",null,"Now we want to make a backward-compatible change to ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File")," and add an ",(0,i.mdx)("inlineCode",{parentName:"p"},"extension")," field. We could add this to the file:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema src.2 {\n predicate File {\n path : string,\n extension : string\n }\n}\n\nschema src.2 evolves src.1\n")),(0,i.mdx)("p",null,"Now if the indexer is still producing only ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.1")," facts, all other predicates will work as before and queries for ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.2")," will return no results."),(0,i.mdx)("p",null,"Once the indexer is changed to produce only ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.2")," facts queries like ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.1 _")," will be fulfilled using data from the ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.2")," schema, converting the ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.2")," results to the shape of ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.1")," before returning to the client."),(0,i.mdx)("p",null,"This is also the case in the derivation query of ",(0,i.mdx)("inlineCode",{parentName:"p"},"info.IsTemporary"),". Although ",(0,i.mdx)("inlineCode",{parentName:"p"},"info")," imports ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.1"),", the query will be transformed to use ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.2")," facts."),(0,i.mdx)("p",null,"On the other hand, ",(0,i.mdx)("inlineCode",{parentName:"p"},"os.Permissions")," will be empty. This must be the case because its first field references a ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.1")," fact, of which there is none in the database. For this predicate to continue being available we must evolve its schema as well."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema os.2 { # changed\n import src.2 # changed\n\n predicate Permissions {\n file : File,\n permissions : nat\n }\n}\n\nschema os.2 evolves os.1 # changed\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/21418ead.a068d6b1.js b/assets/js/21418ead.a068d6b1.js deleted file mode 100644 index 4a4413674..000000000 --- a/assets/js/21418ead.a068d6b1.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[515],{15680:(e,n,a)=>{a.r(n),a.d(n,{MDXContext:()=>s,MDXProvider:()=>h,mdx:()=>x,useMDXComponents:()=>c,withMDXComponents:()=>m});var t=a(96540);function i(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function l(){return l=Object.assign||function(e){for(var n=1;n=0||(i[a]=e[a]);return i}(e,n);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var s=t.createContext({}),m=function(e){return function(n){var a=c(n.components);return t.createElement(e,l({},n,{components:a}))}},c=function(e){var n=t.useContext(s),a=n;return e&&(a="function"==typeof e?e(n):o(o({},n),e)),a},h=function(e){var n=c(e.components);return t.createElement(s.Provider,{value:n},e.children)},p="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},f=t.forwardRef((function(e,n){var a=e.components,i=e.mdxType,l=e.originalType,r=e.parentName,s=d(e,["components","mdxType","originalType","parentName"]),m=c(a),h=i,p=m["".concat(r,".").concat(h)]||m[h]||u[h]||l;return a?t.createElement(p,o(o({ref:n},s),{},{components:a})):t.createElement(p,o({ref:n},s))}));function x(e,n){var a=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var l=a.length,r=new Array(l);r[0]=f;var o={};for(var d in n)hasOwnProperty.call(n,d)&&(o[d]=n[d]);o.originalType=e,o[p]="string"==typeof e?e:i,r[1]=o;for(var s=2;s{a.r(n),a.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var t=a(58168),i=(a(96540),a(15680));const l={id:"changing",title:"How do I change a schema?",sidebar_label:"Changing a schema"},r=void 0,o={unversionedId:"schema/changing",id:"schema/changing",title:"How do I change a schema?",description:"Glean supports modifying the schema directly, while providing",source:"@site/docs/schema/changing.md",sourceDirName:"schema",slug:"/schema/changing",permalink:"/docs/schema/changing",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/schema/changing.md",tags:[],version:"current",frontMatter:{id:"changing",title:"How do I change a schema?",sidebar_label:"Changing a schema"},sidebar:"someSidebar",previous:{title:"Recursion",permalink:"/docs/schema/recursion"},next:{title:'The special "all" schema',permalink:"/docs/schema/all"}},d={},s=[{value:"Basic rules",id:"basic-rules",level:3},{value:"Compatibility",id:"compatibility",level:3},{value:"Default values",id:"default-values",level:3},{value:"What if my schema changes are incompatible?",id:"what-if-my-schema-changes-are-incompatible",level:3},{value:"How does it work?",id:"how-does-it-work",level:3},{value:"Evolving schemas",id:"evolving-schemas",level:3}],m=(c="FbInternalOnly",function(e){return console.warn("Component "+c+" was not imported, exported, or provided by MDXProvider as global scope"),(0,i.mdx)("div",e)});var c;const h={toc:s},p="wrapper";function u(e){let{components:n,...a}=e;return(0,i.mdx)(p,(0,t.A)({},h,a,{components:n,mdxType:"MDXLayout"}),(0,i.mdx)("p",null,"Glean supports modifying the schema directly, while providing\nbackwards compatibility between existing clients and data across the\nschema change."),(0,i.mdx)("h3",{id:"basic-rules"},"Basic rules"),(0,i.mdx)("p",null,"To preserve compatibility between clients and data, the schema can\nonly be changed in ",(0,i.mdx)("strong",{parentName:"p"},"compatible")," ways. This means:"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"Adding or removing a field from a record, if the field has a ",(0,i.mdx)("strong",{parentName:"li"},"defaultable")," type (see ",(0,i.mdx)("a",{parentName:"li",href:"#default-values"},"Default values"),")"),(0,i.mdx)("li",{parentName:"ul"},"Adding or removing an alternative from a sum type"),(0,i.mdx)("li",{parentName:"ul"},"Adding or removing a predicate or type declaration")),(0,i.mdx)("p",null,"An example of an ",(0,i.mdx)("em",{parentName:"p"},"incompatible")," change would be changing the type of a\nfield, for example from ",(0,i.mdx)("inlineCode",{parentName:"p"},"nat")," to ",(0,i.mdx)("inlineCode",{parentName:"p"},"bool"),"."),(0,i.mdx)("p",null,"Of course it's fine to make arbitrary changes to a schema that you're\nworking on; the compatibility rules only come into effect when the\nschema is landed. From that point there might be existing clients and\ndatabases in use, so Glean will reject any incompatible schema changes."),(0,i.mdx)(m,{mdxType:"FbInternalOnly"},"The compatibility check shows up in CI as `sync-cf-main-diff` and is performed on any diff that changes a schema. It checks that the new schema is compatible with the existing schema and all previous versions of the schema still in use, so it will catch things like removing a field and then re-adding it with a different type."),(0,i.mdx)("h3",{id:"compatibility"},"Compatibility"),(0,i.mdx)("p",null,"When the schema is changed, Glean supports any combination of old or\nnew clients (that is, clients built against the old or new version of\nthe schema) with old or new data."),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"If the client requests a field that doesn't exist in the data, the field will be filled with its default value."),(0,i.mdx)("li",{parentName:"ul"},"If a query matches on an alternative that doesn't exist in the schema, the match will fail."),(0,i.mdx)("li",{parentName:"ul"},"If the data contains an alternative that doesn't exist in the client's schema, then the client will receive an ",(0,i.mdx)("inlineCode",{parentName:"li"},"unknown")," value (this is represented as an ",(0,i.mdx)("inlineCode",{parentName:"li"},"EMPTY")," constructor in the Thrift-generated datatypes).")),(0,i.mdx)("h3",{id:"default-values"},"Default values"),(0,i.mdx)("p",null,"A ",(0,i.mdx)("strong",{parentName:"p"},"defaultable")," type is any type that is not a predicate. So for\nexample, if ",(0,i.mdx)("inlineCode",{parentName:"p"},"P")," is a predicate type, then we could add a field ",(0,i.mdx)("inlineCode",{parentName:"p"},"f :\nmaybe P")," but not a field ",(0,i.mdx)("inlineCode",{parentName:"p"},"f : P"),". The reason for this restriction is\nthat Glean must be able to substitute a default value for the\nfield when a client is using the new schema but the data is using the\nold schema, and there cannot be a default value for a predicate."),(0,i.mdx)("p",null,"Default values for missing fields are determined according to the\nfollowing table:"),(0,i.mdx)("table",null,(0,i.mdx)("thead",{parentName:"table"},(0,i.mdx)("tr",{parentName:"thead"},(0,i.mdx)("th",{parentName:"tr",align:null},"Type"),(0,i.mdx)("th",{parentName:"tr",align:null},"Default value"))),(0,i.mdx)("tbody",{parentName:"table"},(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"nat")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"0"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"byte")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"0"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"string")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,'""'))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"[T]")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"[]"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"{ field\u2081 : T\u2081, ..., field\u2099 : T\u2099 }")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"{ field\u2081 = default(T\u2081), ..., field\u2099 = default(T\u2099) }"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"{ field\u2081 : T\u2081 ","|"," ... ","|"," field\u2099 : T\u2099 }")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"{ field\u2081 = default(T\u2081) }"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"bool")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"false"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"maybe T")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"nothing"))),(0,i.mdx)("tr",{parentName:"tbody"},(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"enum { name\u2081 ","|"," ... ","|"," name\u2099 }")),(0,i.mdx)("td",{parentName:"tr",align:null},(0,i.mdx)("code",null,"name\u2081"))))),(0,i.mdx)("h3",{id:"what-if-my-schema-changes-are-incompatible"},"What if my schema changes are incompatible?"),(0,i.mdx)("p",null,"If you don't care about backwards compatibility, then an easy\nworkaround is to just bump the version of the whole schema, so for\nexample if you have"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema graphql.1 {\n...\n}\n")),(0,i.mdx)("p",null,"then change it to"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema graphql.2 {\n...\n}\n")),(0,i.mdx)("p",null,"and make whatever changes you need. The new version of the schema is\nentirely separate from the old version as far as Glean is concerned,\nso there are no restrictions on what changes can be made."),(0,i.mdx)("p",null,"If you want to retain some backwards compatibility, then you can add\nnew predicates representing the new data. You have the option of"),(0,i.mdx)("ol",null,(0,i.mdx)("li",{parentName:"ol"},"Writing both the old and the new data to the database"),(0,i.mdx)("li",{parentName:"ol"},"Producing two databases, one with the old data and one with the new data. The databases can be distinguished by different names or different properties.")),(0,i.mdx)("p",null,"With approach (1), you can migrate clients incrementally and then eventually\nstop producing the old data. But the downside of this approach is that\nthe database may contain a lot more data than necessary; writing may\ntake a lot longer, and so on."),(0,i.mdx)("p",null,"With approach (2), you first have to ensure clients select the old\ndatabase. Then as you migrate clients, change them to use the new data\nand select the new database at the same time."),(0,i.mdx)("h3",{id:"how-does-it-work"},"How does it work?"),(0,i.mdx)("p",null,"A particular instance of the schema is identified by a\n",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId"),". This is a hash value computed from the full contents of\nthe schema. The ",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId")," of the current schema is available through\nthe ",(0,i.mdx)("inlineCode",{parentName:"p"},"schema_id")," value exported by the ",(0,i.mdx)("inlineCode",{parentName:"p"},"builtin")," schema (the\n",(0,i.mdx)("inlineCode",{parentName:"p"},"Glean.Schema.Builtin.Types")," module in Haskell)."),(0,i.mdx)("p",null,"The server keeps track of multiple instances of the schema in a\n",(0,i.mdx)("strong",{parentName:"p"},"schema index"),". Each instance of the schema is identified by its\n",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId"),". Each database also contains the schema that it was written\nwith. The schema index is manipulated using the ",(0,i.mdx)("inlineCode",{parentName:"p"},"gen-schema")," tool in\nthe Glean repository; to use a particular index we can use ",(0,i.mdx)("inlineCode",{parentName:"p"},"--schema\nindexfile:FILE")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"--schema indexconfig:CONFIG"),"."),(0,i.mdx)("p",null,"A client sends its ",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId")," to the server in the ",(0,i.mdx)("inlineCode",{parentName:"p"},"schema_id")," field\nof the query. For Haskell clients this is done automatically: the\nclient passes its ",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId")," when initialising the Glean client\nlibrary, and this ",(0,i.mdx)("inlineCode",{parentName:"p"},"SchemaId")," is passed on to the server for every\nquery."),(0,i.mdx)("p",null,"The server knows which schema the client is using, so it can translate\nthe data in the database into the client's schema automatically."),(0,i.mdx)("p",null,"When a database is created, the schema used by the database is chosen\nby the ",(0,i.mdx)("inlineCode",{parentName:"p"},"glean.schema_id")," property set by the client creating the\ndatabase. Again for Haskell clients this happens automatically."),(0,i.mdx)("h3",{id:"evolving-schemas"},"Evolving schemas"),(0,i.mdx)("p",null,"Glean also allows backwards-compatibility between co-existing schemas,\nwhich can be useful if you want to perform schema changes in a more\nexplicit way, or to rename schemas."),(0,i.mdx)("p",null,"The feature is enabled using a top-level directive"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema my_schema.2 evolves my_schema.1\n")),(0,i.mdx)("p",null,"This declaration has the effect of treating queries for ",(0,i.mdx)("inlineCode",{parentName:"p"},"my_schema.1")," predicates as if they were for ",(0,i.mdx)("inlineCode",{parentName:"p"},"my_schema.2"),". That is the query results will be retrieved from the database in the shape of a ",(0,i.mdx)("inlineCode",{parentName:"p"},"my_schema.2")," fact and transformed into a fact of the equivalent ",(0,i.mdx)("inlineCode",{parentName:"p"},"my_schema.1")," predicate specified in the query."),(0,i.mdx)("p",null,"The new schema must contain all the predicates of the old schema, either with new versions or old versions, and their definitions must be backwards compatible. We can achieve this by copying the entire content of the old schema into the new one and modifying it there."),(0,i.mdx)("p",null,"Now what should Glean do when a client asks for a fact from an old schema?"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"Answer with db facts from the old schema"),(0,i.mdx)("li",{parentName:"ul"},"Answer with db facts from the new schema transformed into the old ones.")),(0,i.mdx)("p",null,"If there are no facts of the old schema in in the database we will take option 2.\nIf the database has any fact at all of the old schema we choose option 1."),(0,i.mdx)("p",null,"That is, schema evolutions only take effect if there are no facts of the old schema in the database; it is ignored otherwise."),(0,i.mdx)("p",null,"As an example suppose we start with the following schemas:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'schema src.1 {\n predicate File {\n path : string\n }\n}\n\nschema os.1 {\n import src.1\n\n predicate Permissions {\n file : File,\n permissions : nat\n }\n}\n\nschema info.1 {\n import src.1\n\n predicate IsTemporary {\n file : File\n } F where F = src.File { path = "/tmp".. }\n}\n')),(0,i.mdx)("p",null,"Now we want to make a backward-compatible change to ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File")," and add an ",(0,i.mdx)("inlineCode",{parentName:"p"},"extension")," field. We could add this to the file:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema src.2 {\n predicate File {\n path : string,\n extension : string\n }\n}\n\nschema src.2 evolves src.1\n")),(0,i.mdx)("p",null,"Now if the indexer is still producing only ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.1")," facts, all other predicates will work as before and queries for ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.2")," will return no results."),(0,i.mdx)("p",null,"Once the indexer is changed to produce only ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.2")," facts queries like ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.1 _")," will be fulfilled using data from the ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.2")," schema, converting the ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.2")," results to the shape of ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.1")," before returning to the client."),(0,i.mdx)("p",null,"This is also the case in the derivation query of ",(0,i.mdx)("inlineCode",{parentName:"p"},"info.IsTemporary"),". Although ",(0,i.mdx)("inlineCode",{parentName:"p"},"info")," imports ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.1"),", the query will be transformed to use ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.2")," facts."),(0,i.mdx)("p",null,"On the other hand, ",(0,i.mdx)("inlineCode",{parentName:"p"},"os.Permissions")," will be empty. This must be the case because its first field references a ",(0,i.mdx)("inlineCode",{parentName:"p"},"src.File.1")," fact, of which there is none in the database. For this predicate to continue being available we must evolve its schema as well."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"schema os.2 { # changed\n import src.2 # changed\n\n predicate Permissions {\n file : File,\n permissions : nat\n }\n}\n\nschema os.2 evolves os.1 # changed\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/283d7b21.5e2f56ef.js b/assets/js/283d7b21.5e2f56ef.js deleted file mode 100644 index d8d4e475c..000000000 --- a/assets/js/283d7b21.5e2f56ef.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[76],{15680:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>c,MDXProvider:()=>m,mdx:()=>y,useMDXComponents:()=>u,withMDXComponents:()=>d});var r=n(96540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(){return i=Object.assign||function(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var c=r.createContext({}),d=function(e){return function(t){var n=u(t.components);return r.createElement(e,i({},t,{components:n}))}},u=function(e){var t=r.useContext(c),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},m=function(e){var t=u(e.components);return r.createElement(c.Provider,{value:t},e.children)},p="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,o=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),d=u(n),m=a,p=d["".concat(o,".").concat(m)]||d[m]||h[m]||i;return n?r.createElement(p,l(l({ref:t},c),{},{components:n})):r.createElement(p,l({ref:t},c))}));function y(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,o=new Array(i);o[0]=f;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[p]="string"==typeof e?e:a,o[1]=l;for(var c=2;c{n.d(t,{D4:()=>o,Lw:()=>l,gD:()=>s});var r=n(96540),a=n(14423);let i;function o(e){return r.createElement("a",{href:i+e.file},e.file)}function l(e){return r.createElement("a",{href:i+e.file},e.children)}i=(0,a.isInternal)()?"https://www.internalfb.com/code/fbsource/fbcode/":"https://github.com/facebookincubator/Glean/tree/master/";const s=e=>{let{children:t,internal:n,external:i}=e;return(0,a.fbContent)({internal:r.createElement("code",null,n),external:r.createElement("code",null,i)})}},51396:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var r=n(58168),a=(n(96540),n(15680));n(67555);const i={id:"design",title:"Schema Design",sidebar_label:"Design"},o=void 0,l={unversionedId:"schema/design",id:"schema/design",title:"Schema Design",description:"There are usually multiple ways to design a schema, and which one is",source:"@site/docs/schema/design.md",sourceDirName:"schema",slug:"/schema/design",permalink:"/docs/schema/design",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/schema/design.md",tags:[],version:"current",frontMatter:{id:"design",title:"Schema Design",sidebar_label:"Design"},sidebar:"someSidebar",previous:{title:"Thrift and JSON",permalink:"/docs/schema/thrift"},next:{title:"Overview",permalink:"/docs/query/intro"}},s={},c=[{value:"Should we reference predicates directly?",id:"should-we-reference-predicates-directly",level:2},{value:"Use key-value predicates",id:"use-key-value-predicates",level:2},{value:"Using arrays",id:"using-arrays",level:2},{value:"Increase sharing",id:"increase-sharing",level:2},{value:"How to experiment with schema design",id:"how-to-experiment-with-schema-design",level:2}],d={toc:c},u="wrapper";function m(e){let{components:t,...n}=e;return(0,a.mdx)(u,(0,r.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"There are usually multiple ways to design a schema, and which one is\nbest will depend on multiple factors. Specifically, we usually want to\nrepresent data so that it"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},"can be stored compactly,"),(0,a.mdx)("li",{parentName:"ul"},"is convenient to generate,"),(0,a.mdx)("li",{parentName:"ul"},"is convenient and efficient to query,"),(0,a.mdx)("li",{parentName:"ul"},"and it supports incremental indexing.")),(0,a.mdx)("p",null,"In the following sections we'll go through some of the common choices\nthat you'll encounter when designing a schema and offer some advice."),(0,a.mdx)("p",null,"Note: here we're only concerned with stored facts. The considerations\nhere don't apply to ",(0,a.mdx)("a",{parentName:"p",href:"../../derived/#on-demand-derived-predicates"},"On-demand derived predicates"),", because they aren't stored."),(0,a.mdx)("h2",{id:"should-we-reference-predicates-directly"},"Should we reference predicates directly?"),(0,a.mdx)("p",null,"For example, you could write a predicate for a class like this: ",(0,a.mdx)("strong",{parentName:"p"},"(1)")),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"predicate Class :\n {\n name : string,\n methods : [Method]\n }\n")),(0,a.mdx)("p",null,"or like this: ",(0,a.mdx)("strong",{parentName:"p"},"(2)")),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"predicate Class :\n {\n name : string,\n }\n\npredicate ClassMethod :\n {\n class_ : Class,\n method : Method\n }\n")),(0,a.mdx)("p",null,"Which of these is best?"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Functionality")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"(1) retains the order of the methods, which might be\nimportant. Otherwise they're equivalent."),(0,a.mdx)("li",{parentName:"ul"},"With (1) we have to know the methods when we generate the ",(0,a.mdx)("inlineCode",{parentName:"li"},"Class"),"\nfact, whereas with (2) we can generate the facts about the methods\nseparately and in any order. This might not matter much with\nsomething small like a class definition, but for larger facts\n(e.g. the definitions of a file) it could be important."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Data representation")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"(1) has fewer facts per class, so is more compact (see ",(0,a.mdx)("a",{parentName:"li",href:"#using-arrays"},"Using Arrays")," below)."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Query performance")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"it's faster to fetch the methods of a class with\n(1), because (2) requires searching two predicates."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Incrementality")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"with (1), changing one method requires changing the\nwhole ",(0,a.mdx)("inlineCode",{parentName:"li"},"Class")," fact, which might force changes to other facts. With\n(2) we would only need to replace the ",(0,a.mdx)("inlineCode",{parentName:"li"},"ClassToMethod")," fact.")))),(0,a.mdx)("h2",{id:"use-key-value-predicates"},"Use key-value predicates"),(0,a.mdx)("p",null,"We often have a choice between using key-only or key-value (also known as ",(0,a.mdx)("a",{parentName:"p",href:"../../angle/advanced#functional-predicates"},"Functional predicates"),"): ",(0,a.mdx)("strong",{parentName:"p"},"(1)")),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"predicate FunctionType : { function : Function, type_ : Type }\n")),(0,a.mdx)("p",null,"and: ",(0,a.mdx)("strong",{parentName:"p"},"(2)")),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"predicate FunctionType : Function -> Type\n")),(0,a.mdx)("p",null,"There are several tradeoffs here:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Functionality")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"(1) is a relation, whereas (2) is a function. In practical terms,\nwith (1) you can have many types for the same function, but with\n(2) that is an error (Glean will complain if you try to\ninsert two facts with the same key and different values)."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Data representation")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"(2) is much more efficient to store. In particular the value is\nstored only once. If the value (",(0,a.mdx)("inlineCode",{parentName:"li"},"Type")," in the above example) is large,\nyou should strongly consider using a key-value predicate."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Query performance")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"Both (1) and (2) support efficient querying by the key (",(0,a.mdx)("inlineCode",{parentName:"li"},"Function"),"\nin the example), and they both support slow filtering by the value\n(",(0,a.mdx)("inlineCode",{parentName:"li"},"Type"),")."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Incrementality")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"These two alternatives are equivalent with respect to incrementality.")))),(0,a.mdx)("h2",{id:"using-arrays"},"Using arrays"),(0,a.mdx)("p",null,"If you're choosing between arrays and separate facts, then consider:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Arrays are ordered lists, whereas facts are just sets. If the order\nof your items is important - because you're representing something\nthat has an order, such as function arguments - then an array is the\nright choice. (someday Glean might have a \"set\" type, but it\ncurrently doesn't).")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Conversely, if the order is ",(0,a.mdx)("em",{parentName:"p"},"not")," important, then using an array is\na poor choice because you will be forced to choose an order when\ngenerating your data. If you don't have a deterministic way to pick\nthe order, then your data representation is non-deterministic which\nleads to spurious differences in things like test outputs, which can\nbe annoying.")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Arrays are much more compact than multiple facts. There can be a\nhuge difference in storage overhead; it's worth measuring this for\nyour schema.")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"When a client fetches an array as part of the result of a query,\nthey will get the whole array. If your array is large, that may be a\nlot of data to send over the wire, and it might even result in an\nallocation limit error on the server, preventing the client from\nfetching the data at all. Facts tend to support incremental querying\nbetter compared with arrays.")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Facts with large arrays are also slower to search through in a query\nthan smaller facts."))),(0,a.mdx)("h2",{id:"increase-sharing"},"Increase sharing"),(0,a.mdx)("p",null,"If there is duplication in the data stored in our facts, we can often\nextract the common data into a predicate to increase sharing. One\nexample of this was described in ",(0,a.mdx)("a",{parentName:"p",href:"/docs/schema/syntax#what-is-the-difference-between-a-predicate-and-a-type"},"What is the difference between a predicate and a type?"),"."),(0,a.mdx)("h2",{id:"how-to-experiment-with-schema-design"},"How to experiment with schema design"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Generate some data and see how large it is, using ",(0,a.mdx)("inlineCode",{parentName:"p"},":stat")," in the shell.")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Write some example queries against your data, and check how much\nsearching they do using ",(0,a.mdx)("inlineCode",{parentName:"p"},":profile")," in the shell (see ",(0,a.mdx)("a",{parentName:"p",href:"/docs/angle/debugging"},"Query\nDebugging"),")."))))}m.isMDXComponent=!0},80510:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{s(r.next(e))}catch(t){i(t)}}function l(e){try{s(r.throw(e))}catch(t){i(t)}}function s(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,l)}s((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getSpecInfo=void 0;const a=n(88266);t.getSpecInfo=function(e){return r(this,void 0,void 0,(function*(){return yield a.call({module:"bloks",api:"getSpecInfo",args:{styleId:e}})}))}},88266:function(e,t){var n=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{s(r.next(e))}catch(t){i(t)}}function l(e){try{s(r.throw(e))}catch(t){i(t)}}function s(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,l)}s((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.call=void 0;let r=!1,a=0;const i={};t.call=function(e){return n(this,void 0,void 0,(function*(){if("staticdocs.thefacebook.com"!==window.location.hostname&&"localhost"!==window.location.hostname)return Promise.reject(new Error("Not running on static docs"));r||(r=!0,window.addEventListener("message",(e=>{if("static-docs-bridge-response"!==e.data.event)return;const t=e.data.id;t in i||console.error(`Recieved response for id: ${t} with no matching receiver`),"response"in e.data?i[t].resolve(e.data.response):i[t].reject(new Error(e.data.error)),delete i[t]})));const t=a++,n=new Promise(((e,n)=>{i[t]={resolve:e,reject:n}})),o={event:"static-docs-bridge-call",id:t,module:e.module,api:e.api,args:e.args},l="localhost"===window.location.hostname?"*":"https://www.internalfb.com";return window.parent.postMessage(o,l),n}))}},70680:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{s(r.next(e))}catch(t){i(t)}}function l(e){try{s(r.throw(e))}catch(t){i(t)}}function s(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,l)}s((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.reportFeatureUsage=t.reportContentCopied=void 0;const a=n(88266);t.reportContentCopied=function(e){return r(this,void 0,void 0,(function*(){const{textContent:t}=e;try{yield a.call({module:"feedback",api:"reportContentCopied",args:{textContent:t}})}catch(n){}}))},t.reportFeatureUsage=function(e){return r(this,void 0,void 0,(function*(){const{featureName:t,id:n}=e;console.log("used feature");try{yield a.call({module:"feedback",api:"reportFeatureUsage",args:{featureName:t,id:n}})}catch(r){}}))}},14423:function(e,t,n){var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),a=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&r(t,e,n);return a(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.OssOnly=t.FbInternalOnly=t.getEphemeralDiffNumber=t.hasEphemeralDiffNumber=t.isInternal=t.validateFbContentArgs=t.fbInternalOnly=t.fbContent=t.inpageeditor=t.feedback=t.uidocs=t.bloks=void 0,t.bloks=i(n(80510)),t.uidocs=i(n(3730)),t.feedback=i(n(70680)),t.inpageeditor=i(n(45458));const o=["internal","external"];function l(e){return c(e),d()?"internal"in e?s(e.internal):[]:"external"in e?s(e.external):[]}function s(e){return"function"==typeof e?e():e}function c(e){if("object"!=typeof e)throw new Error(`fbContent() args must be an object containing keys: ${o}. Instead got ${e}`);if(!Object.keys(e).find((e=>o.find((t=>t===e)))))throw new Error(`No valid args found in ${JSON.stringify(e)}. Accepted keys: ${o}`);const t=Object.keys(e).filter((e=>!o.find((t=>t===e))));if(t.length>0)throw new Error(`Unexpected keys ${t} found in fbContent() args. Accepted keys: ${o}`)}function d(){try{return Boolean(!1)}catch(e){return console.log("process.env.FB_INTERNAL couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),!1}}function u(){try{return null}catch(e){return console.log("process.env.PHABRICATOR_DIFF_NUMBER couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),null}}t.fbContent=l,t.fbInternalOnly=function(e){return l({internal:e})},t.validateFbContentArgs=c,t.isInternal=d,t.hasEphemeralDiffNumber=function(){return Boolean(u())},t.getEphemeralDiffNumber=u,t.FbInternalOnly=function(e){return d()?e.children:null},t.OssOnly=function(e){return d()?null:e.children}},45458:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{s(r.next(e))}catch(t){i(t)}}function l(e){try{s(r.throw(e))}catch(t){i(t)}}function s(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,l)}s((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.submitDiff=void 0;const a=n(88266);t.submitDiff=function(e){return r(this,void 0,void 0,(function*(){const{file_path:t,new_content:n,project_name:r,diff_number:i}=e;try{return yield a.call({module:"inpageeditor",api:"createPhabricatorDiffApi",args:{file_path:t,new_content:n,project_name:r,diff_number:i}})}catch(o){throw new Error(`Error occurred while trying to submit diff. Stack trace: ${o}`)}}))}},3730:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{s(r.next(e))}catch(t){i(t)}}function l(e){try{s(r.throw(e))}catch(t){i(t)}}function s(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,l)}s((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getApi=t.docsets=void 0;const a=n(88266);t.docsets={BLOKS_CORE:"887372105406659"},t.getApi=function(e){return r(this,void 0,void 0,(function*(){const{name:t,framework:n,docset:r}=e;return yield a.call({module:"uidocs",api:"getApi",args:{name:t,framework:n,docset:r}})}))}}}]); \ No newline at end of file diff --git a/assets/js/283d7b21.d61de8d0.js b/assets/js/283d7b21.d61de8d0.js new file mode 100644 index 000000000..e96d03290 --- /dev/null +++ b/assets/js/283d7b21.d61de8d0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[76],{15680:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>c,MDXProvider:()=>m,mdx:()=>y,useMDXComponents:()=>u,withMDXComponents:()=>d});var r=n(96540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(){return i=Object.assign||function(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var c=r.createContext({}),d=function(e){return function(t){var n=u(t.components);return r.createElement(e,i({},t,{components:n}))}},u=function(e){var t=r.useContext(c),n=t;return e&&(n="function"==typeof e?e(t):s(s({},t),e)),n},m=function(e){var t=u(e.components);return r.createElement(c.Provider,{value:t},e.children)},p="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,o=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=u(n),m=a,p=d["".concat(o,".").concat(m)]||d[m]||h[m]||i;return n?r.createElement(p,s(s({ref:t},c),{},{components:n})):r.createElement(p,s({ref:t},c))}));function y(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,o=new Array(i);o[0]=f;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[p]="string"==typeof e?e:a,o[1]=s;for(var c=2;c{n.d(t,{D4:()=>o,Lw:()=>s,gD:()=>l});var r=n(96540),a=n(14423);let i;function o(e){return r.createElement("a",{href:i+e.file},e.file)}function s(e){return r.createElement("a",{href:i+e.file},e.children)}i=(0,a.isInternal)()?"https://www.internalfb.com/code/fbsource/fbcode/":"https://github.com/facebookincubator/Glean/tree/master/";const l=e=>{let{children:t,internal:n,external:i}=e;return(0,a.fbContent)({internal:r.createElement("code",null,n),external:r.createElement("code",null,i)})}},51396:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var r=n(58168),a=(n(96540),n(15680));n(67555);const i={id:"design",title:"Schema Design",sidebar_label:"Design"},o=void 0,s={unversionedId:"schema/design",id:"schema/design",title:"Schema Design",description:"There are usually multiple ways to design a schema, and which one is",source:"@site/docs/schema/design.md",sourceDirName:"schema",slug:"/schema/design",permalink:"/docs/schema/design",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/schema/design.md",tags:[],version:"current",frontMatter:{id:"design",title:"Schema Design",sidebar_label:"Design"},sidebar:"someSidebar",previous:{title:"Thrift and JSON",permalink:"/docs/schema/thrift"},next:{title:"Overview",permalink:"/docs/query/intro"}},l={},c=[{value:"Should we reference predicates directly?",id:"should-we-reference-predicates-directly",level:2},{value:"Use key-value predicates",id:"use-key-value-predicates",level:2},{value:"Using arrays, sets or separate facts",id:"using-arrays-sets-or-separate-facts",level:2},{value:"Increase sharing",id:"increase-sharing",level:2},{value:"How to experiment with schema design",id:"how-to-experiment-with-schema-design",level:2}],d={toc:c},u="wrapper";function m(e){let{components:t,...n}=e;return(0,a.mdx)(u,(0,r.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"There are usually multiple ways to design a schema, and which one is\nbest will depend on multiple factors. Specifically, we usually want to\nrepresent data so that it"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},"can be stored compactly,"),(0,a.mdx)("li",{parentName:"ul"},"is convenient to generate,"),(0,a.mdx)("li",{parentName:"ul"},"is convenient and efficient to query,"),(0,a.mdx)("li",{parentName:"ul"},"and it supports incremental indexing.")),(0,a.mdx)("p",null,"In the following sections we'll go through some of the common choices\nthat you'll encounter when designing a schema and offer some advice."),(0,a.mdx)("p",null,"Note: here we're only concerned with stored facts. The considerations\nhere don't apply to ",(0,a.mdx)("a",{parentName:"p",href:"../../derived/#on-demand-derived-predicates"},"On-demand derived predicates"),", because they aren't stored."),(0,a.mdx)("h2",{id:"should-we-reference-predicates-directly"},"Should we reference predicates directly?"),(0,a.mdx)("p",null,"For example, you could write a predicate for a class like this: ",(0,a.mdx)("strong",{parentName:"p"},"(1)")),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"predicate Class :\n {\n name : string,\n methods : [Method]\n }\n")),(0,a.mdx)("p",null,"or like this: ",(0,a.mdx)("strong",{parentName:"p"},"(2)")),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"predicate Class :\n {\n name : string,\n }\n\npredicate ClassMethod :\n {\n class_ : Class,\n method : Method\n }\n")),(0,a.mdx)("p",null,"Which of these is best?"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Functionality")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"(1) retains the order of the methods, which might be\nimportant. Otherwise they're equivalent."),(0,a.mdx)("li",{parentName:"ul"},"With (1) we have to know the methods when we generate the ",(0,a.mdx)("inlineCode",{parentName:"li"},"Class"),"\nfact, whereas with (2) we can generate the facts about the methods\nseparately and in any order. This might not matter much with\nsomething small like a class definition, but for larger facts\n(e.g. the definitions of a file) it could be important."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Data representation")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"(1) has fewer facts per class, so is more compact (see ",(0,a.mdx)("a",{parentName:"li",href:"#using-arrays"},"Using Arrays")," below)."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Query performance")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"it's faster to fetch the methods of a class with\n(1), because (2) requires searching two predicates."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Incrementality")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"with (1), changing one method requires changing the\nwhole ",(0,a.mdx)("inlineCode",{parentName:"li"},"Class")," fact, which might force changes to other facts. With\n(2) we would only need to replace the ",(0,a.mdx)("inlineCode",{parentName:"li"},"ClassToMethod")," fact.")))),(0,a.mdx)("h2",{id:"use-key-value-predicates"},"Use key-value predicates"),(0,a.mdx)("p",null,"We often have a choice between using key-only or key-value (also known as ",(0,a.mdx)("a",{parentName:"p",href:"../../angle/advanced#functional-predicates"},"Functional predicates"),"): ",(0,a.mdx)("strong",{parentName:"p"},"(1)")),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"predicate FunctionType : { function : Function, type_ : Type }\n")),(0,a.mdx)("p",null,"and: ",(0,a.mdx)("strong",{parentName:"p"},"(2)")),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"predicate FunctionType : Function -> Type\n")),(0,a.mdx)("p",null,"There are several tradeoffs here:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Functionality")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"(1) is a relation, whereas (2) is a function. In practical terms,\nwith (1) you can have many types for the same function, but with\n(2) that is an error (Glean will complain if you try to\ninsert two facts with the same key and different values)."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Data representation")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"(2) is much more efficient to store. In particular the value is\nstored only once. If the value (",(0,a.mdx)("inlineCode",{parentName:"li"},"Type")," in the above example) is large,\nyou should strongly consider using a key-value predicate."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Query performance")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"Both (1) and (2) support efficient querying by the key (",(0,a.mdx)("inlineCode",{parentName:"li"},"Function"),"\nin the example), and they both support slow filtering by the value\n(",(0,a.mdx)("inlineCode",{parentName:"li"},"Type"),")."))),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},(0,a.mdx)("strong",{parentName:"p"},"Incrementality")),(0,a.mdx)("ul",{parentName:"li"},(0,a.mdx)("li",{parentName:"ul"},"These two alternatives are equivalent with respect to incrementality.")))),(0,a.mdx)("h2",{id:"using-arrays-sets-or-separate-facts"},"Using arrays, sets or separate facts"),(0,a.mdx)("p",null,"If you're choosing between arrays and separate facts, then consider:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Arrays are ordered lists, whereas facts are just sets. If the order\nof your items is important - because you're representing something\nthat has an order, such as function arguments - then an array is the\nright choice.")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Conversely, if the order is ",(0,a.mdx)("em",{parentName:"p"},"not")," important, then sets are the natural\nchoice. Using an array is\na poor choice because you will be forced to choose an order when\ngenerating your data. If you don't have a deterministic way to pick\nthe order, then your data representation is non-deterministic which\nleads to spurious differences in things like test outputs, which can\nbe annoying.")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Arrays and sets are much more compact than multiple facts. There can be a\nhuge difference in storage overhead; it's worth measuring this for\nyour schema.")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"When a client fetches an array or a set as part of the result of a query,\nthey will get the whole array/set. If it is large, that may be a\nlot of data to send over the wire, and it might even result in an\nallocation limit error on the server, preventing the client from\nfetching the data at all. Facts tend to support incremental querying\nbetter compared with arrays and sets.")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Facts with large arrays/sets are also slower to search through in a query\nthan smaller facts."))),(0,a.mdx)("h2",{id:"increase-sharing"},"Increase sharing"),(0,a.mdx)("p",null,"If there is duplication in the data stored in our facts, we can often\nextract the common data into a predicate to increase sharing. One\nexample of this was described in ",(0,a.mdx)("a",{parentName:"p",href:"/docs/schema/syntax#what-is-the-difference-between-a-predicate-and-a-type"},"What is the difference between a predicate and a type?"),"."),(0,a.mdx)("p",null,"Choosing to use sets instead of arrays can increase sharing because sets have\na canonical representation."),(0,a.mdx)("h2",{id:"how-to-experiment-with-schema-design"},"How to experiment with schema design"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Generate some data and see how large it is, using ",(0,a.mdx)("inlineCode",{parentName:"p"},":stat")," in the shell.")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("p",{parentName:"li"},"Write some example queries against your data, and check how much\nsearching they do using ",(0,a.mdx)("inlineCode",{parentName:"p"},":profile")," in the shell (see ",(0,a.mdx)("a",{parentName:"p",href:"/docs/angle/debugging"},"Query\nDebugging"),")."))))}m.isMDXComponent=!0},80510:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{l(r.next(e))}catch(t){i(t)}}function s(e){try{l(r.throw(e))}catch(t){i(t)}}function l(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getSpecInfo=void 0;const a=n(88266);t.getSpecInfo=function(e){return r(this,void 0,void 0,(function*(){return yield a.call({module:"bloks",api:"getSpecInfo",args:{styleId:e}})}))}},88266:function(e,t){var n=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{l(r.next(e))}catch(t){i(t)}}function s(e){try{l(r.throw(e))}catch(t){i(t)}}function l(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.call=void 0;let r=!1,a=0;const i={};t.call=function(e){return n(this,void 0,void 0,(function*(){if("staticdocs.thefacebook.com"!==window.location.hostname&&"localhost"!==window.location.hostname)return Promise.reject(new Error("Not running on static docs"));r||(r=!0,window.addEventListener("message",(e=>{if("static-docs-bridge-response"!==e.data.event)return;const t=e.data.id;t in i||console.error(`Recieved response for id: ${t} with no matching receiver`),"response"in e.data?i[t].resolve(e.data.response):i[t].reject(new Error(e.data.error)),delete i[t]})));const t=a++,n=new Promise(((e,n)=>{i[t]={resolve:e,reject:n}})),o={event:"static-docs-bridge-call",id:t,module:e.module,api:e.api,args:e.args},s="localhost"===window.location.hostname?"*":"https://www.internalfb.com";return window.parent.postMessage(o,s),n}))}},70680:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{l(r.next(e))}catch(t){i(t)}}function s(e){try{l(r.throw(e))}catch(t){i(t)}}function l(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.reportFeatureUsage=t.reportContentCopied=void 0;const a=n(88266);t.reportContentCopied=function(e){return r(this,void 0,void 0,(function*(){const{textContent:t}=e;try{yield a.call({module:"feedback",api:"reportContentCopied",args:{textContent:t}})}catch(n){}}))},t.reportFeatureUsage=function(e){return r(this,void 0,void 0,(function*(){const{featureName:t,id:n}=e;console.log("used feature");try{yield a.call({module:"feedback",api:"reportFeatureUsage",args:{featureName:t,id:n}})}catch(r){}}))}},14423:function(e,t,n){var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),a=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&r(t,e,n);return a(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.OssOnly=t.FbInternalOnly=t.getEphemeralDiffNumber=t.hasEphemeralDiffNumber=t.isInternal=t.validateFbContentArgs=t.fbInternalOnly=t.fbContent=t.inpageeditor=t.feedback=t.uidocs=t.bloks=void 0,t.bloks=i(n(80510)),t.uidocs=i(n(3730)),t.feedback=i(n(70680)),t.inpageeditor=i(n(45458));const o=["internal","external"];function s(e){return c(e),d()?"internal"in e?l(e.internal):[]:"external"in e?l(e.external):[]}function l(e){return"function"==typeof e?e():e}function c(e){if("object"!=typeof e)throw new Error(`fbContent() args must be an object containing keys: ${o}. Instead got ${e}`);if(!Object.keys(e).find((e=>o.find((t=>t===e)))))throw new Error(`No valid args found in ${JSON.stringify(e)}. Accepted keys: ${o}`);const t=Object.keys(e).filter((e=>!o.find((t=>t===e))));if(t.length>0)throw new Error(`Unexpected keys ${t} found in fbContent() args. Accepted keys: ${o}`)}function d(){try{return Boolean(!1)}catch(e){return console.log("process.env.FB_INTERNAL couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),!1}}function u(){try{return null}catch(e){return console.log("process.env.PHABRICATOR_DIFF_NUMBER couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),null}}t.fbContent=s,t.fbInternalOnly=function(e){return s({internal:e})},t.validateFbContentArgs=c,t.isInternal=d,t.hasEphemeralDiffNumber=function(){return Boolean(u())},t.getEphemeralDiffNumber=u,t.FbInternalOnly=function(e){return d()?e.children:null},t.OssOnly=function(e){return d()?null:e.children}},45458:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{l(r.next(e))}catch(t){i(t)}}function s(e){try{l(r.throw(e))}catch(t){i(t)}}function l(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.submitDiff=void 0;const a=n(88266);t.submitDiff=function(e){return r(this,void 0,void 0,(function*(){const{file_path:t,new_content:n,project_name:r,diff_number:i}=e;try{return yield a.call({module:"inpageeditor",api:"createPhabricatorDiffApi",args:{file_path:t,new_content:n,project_name:r,diff_number:i}})}catch(o){throw new Error(`Error occurred while trying to submit diff. Stack trace: ${o}`)}}))}},3730:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(a,i){function o(e){try{l(r.next(e))}catch(t){i(t)}}function s(e){try{l(r.throw(e))}catch(t){i(t)}}function l(e){var t;e.done?a(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getApi=t.docsets=void 0;const a=n(88266);t.docsets={BLOKS_CORE:"887372105406659"},t.getApi=function(e){return r(this,void 0,void 0,(function*(){const{name:t,framework:n,docset:r}=e;return yield a.call({module:"uidocs",api:"getApi",args:{name:t,framework:n,docset:r}})}))}}}]); \ No newline at end of file diff --git a/assets/js/33b5e0ca.b5e28820.js b/assets/js/33b5e0ca.2540bb03.js similarity index 72% rename from assets/js/33b5e0ca.b5e28820.js rename to assets/js/33b5e0ca.2540bb03.js index fad6a6b36..5164f81a0 100644 --- a/assets/js/33b5e0ca.b5e28820.js +++ b/assets/js/33b5e0ca.2540bb03.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[6669],{15680:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>i,MDXProvider:()=>u,mdx:()=>b,useMDXComponents:()=>c,withMDXComponents:()=>p});var r=n(96540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(){return l=Object.assign||function(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var i=r.createContext({}),p=function(e){return function(t){var n=c(t.components);return r.createElement(e,l({},t,{components:n}))}},c=function(e){var t=r.useContext(i),n=t;return e&&(n="function"==typeof e?e(t):m(m({},t),e)),n},u=function(e){var t=c(e.components);return r.createElement(i.Provider,{value:t},e.children)},s="mdxType",y={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,l=e.originalType,o=e.parentName,i=d(e,["components","mdxType","originalType","parentName"]),p=c(n),u=a,s=p["".concat(o,".").concat(u)]||p[u]||y[u]||l;return n?r.createElement(s,m(m({ref:t},i),{},{components:n})):r.createElement(s,m({ref:t},i))}));function b(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var l=n.length,o=new Array(l);o[0]=f;var m={};for(var d in t)hasOwnProperty.call(t,d)&&(m[d]=t[d]);m.originalType=e,m[s]="string"==typeof e?e:a,o[1]=m;for(var i=2;i{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>m,toc:()=>i});var r=n(58168),a=(n(96540),n(15680));const l={id:"types",title:"Built-in Types",sidebar_label:"Built-in Types"},o=void 0,m={unversionedId:"schema/types",id:"schema/types",title:"Built-in Types",description:"| Type | Meaning |",source:"@site/docs/schema/types.md",sourceDirName:"schema",slug:"/schema/types",permalink:"/docs/schema/types",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/schema/types.md",tags:[],version:"current",frontMatter:{id:"types",title:"Built-in Types",sidebar_label:"Built-in Types"},sidebar:"someSidebar",previous:{title:"Basic Concepts",permalink:"/docs/schema/basic"},next:{title:"Syntax",permalink:"/docs/schema/syntax"}},d={},i=[],p={toc:i},c="wrapper";function u(e){let{components:t,...n}=e;return(0,a.mdx)(c,(0,r.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("table",null,(0,a.mdx)("thead",{parentName:"table"},(0,a.mdx)("tr",{parentName:"thead"},(0,a.mdx)("th",{parentName:"tr",align:null},"Type"),(0,a.mdx)("th",{parentName:"tr",align:null},"Meaning"))),(0,a.mdx)("tbody",{parentName:"table"},(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"nat")),(0,a.mdx)("td",{parentName:"tr",align:null},"64-bit natural numbers")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"byte")),(0,a.mdx)("td",{parentName:"tr",align:null},"8-bit natural numbers")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"string")),(0,a.mdx)("td",{parentName:"tr",align:null},"UTF-8 encoded strings")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"[T]")),(0,a.mdx)("td",{parentName:"tr",align:null},"lists of elements of type T")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"{ field\u2081 : T\u2081, ..., field\u2099 : T\u2099 }")),(0,a.mdx)("td",{parentName:"tr",align:null},"a record with zero or more named fields")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"{ field\u2081 : T\u2081 ","|"," ... ","|"," field\u2099 : T\u2099 }")),(0,a.mdx)("td",{parentName:"tr",align:null},"a sum (union) type with one or more named alternatives")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"P")),(0,a.mdx)("td",{parentName:"tr",align:null},"a reference to a fact of predicate P")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"bool")),(0,a.mdx)("td",{parentName:"tr",align:null},"the boolean type with values ",(0,a.mdx)("strong",{parentName:"td"},"true")," and ",(0,a.mdx)("strong",{parentName:"td"},"false"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"maybe T")),(0,a.mdx)("td",{parentName:"tr",align:null},"an optional value of type T")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"enum { name\u2081 ","|"," ... ","|"," name\u2099 }")),(0,a.mdx)("td",{parentName:"tr",align:null},"exactly one of the symbols name\u2081..name\u2099")))))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[6669],{15680:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>i,MDXProvider:()=>u,mdx:()=>x,useMDXComponents:()=>c,withMDXComponents:()=>p});var r=n(96540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(){return l=Object.assign||function(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var i=r.createContext({}),p=function(e){return function(t){var n=c(t.components);return r.createElement(e,l({},t,{components:n}))}},c=function(e){var t=r.useContext(i),n=t;return e&&(n="function"==typeof e?e(t):m(m({},t),e)),n},u=function(e){var t=c(e.components);return r.createElement(i.Provider,{value:t},e.children)},s="mdxType",y={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,l=e.originalType,o=e.parentName,i=d(e,["components","mdxType","originalType","parentName"]),p=c(n),u=a,s=p["".concat(o,".").concat(u)]||p[u]||y[u]||l;return n?r.createElement(s,m(m({ref:t},i),{},{components:n})):r.createElement(s,m({ref:t},i))}));function x(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var l=n.length,o=new Array(l);o[0]=f;var m={};for(var d in t)hasOwnProperty.call(t,d)&&(m[d]=t[d]);m.originalType=e,m[s]="string"==typeof e?e:a,o[1]=m;for(var i=2;i{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>m,toc:()=>i});var r=n(58168),a=(n(96540),n(15680));const l={id:"types",title:"Built-in Types",sidebar_label:"Built-in Types"},o=void 0,m={unversionedId:"schema/types",id:"schema/types",title:"Built-in Types",description:"| Type | Meaning |",source:"@site/docs/schema/types.md",sourceDirName:"schema",slug:"/schema/types",permalink:"/docs/schema/types",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/schema/types.md",tags:[],version:"current",frontMatter:{id:"types",title:"Built-in Types",sidebar_label:"Built-in Types"},sidebar:"someSidebar",previous:{title:"Basic Concepts",permalink:"/docs/schema/basic"},next:{title:"Syntax",permalink:"/docs/schema/syntax"}},d={},i=[],p={toc:i},c="wrapper";function u(e){let{components:t,...n}=e;return(0,a.mdx)(c,(0,r.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("table",null,(0,a.mdx)("thead",{parentName:"table"},(0,a.mdx)("tr",{parentName:"thead"},(0,a.mdx)("th",{parentName:"tr",align:null},"Type"),(0,a.mdx)("th",{parentName:"tr",align:null},"Meaning"))),(0,a.mdx)("tbody",{parentName:"table"},(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"nat")),(0,a.mdx)("td",{parentName:"tr",align:null},"64-bit natural numbers")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"byte")),(0,a.mdx)("td",{parentName:"tr",align:null},"8-bit natural numbers")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"string")),(0,a.mdx)("td",{parentName:"tr",align:null},"UTF-8 encoded strings")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"[T]")),(0,a.mdx)("td",{parentName:"tr",align:null},"lists of elements of type T")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"set T ")),(0,a.mdx)("td",{parentName:"tr",align:null},"set of elements of type T")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"{ field\u2081 : T\u2081, ..., field\u2099 : T\u2099 }")),(0,a.mdx)("td",{parentName:"tr",align:null},"a record with zero or more named fields")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"{ field\u2081 : T\u2081 ","|"," ... ","|"," field\u2099 : T\u2099 }")),(0,a.mdx)("td",{parentName:"tr",align:null},"a sum (union) type with one or more named alternatives")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"P")),(0,a.mdx)("td",{parentName:"tr",align:null},"a reference to a fact of predicate P")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"bool")),(0,a.mdx)("td",{parentName:"tr",align:null},"the boolean type with values ",(0,a.mdx)("strong",{parentName:"td"},"true")," and ",(0,a.mdx)("strong",{parentName:"td"},"false"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"maybe T")),(0,a.mdx)("td",{parentName:"tr",align:null},"an optional value of type T")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("code",null,"enum { name\u2081 ","|"," ... ","|"," name\u2099 }")),(0,a.mdx)("td",{parentName:"tr",align:null},"exactly one of the symbols name\u2081..name\u2099")))))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/3e65d5b4.459eb74e.js b/assets/js/3e65d5b4.459eb74e.js new file mode 100644 index 000000000..469886e62 --- /dev/null +++ b/assets/js/3e65d5b4.459eb74e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[4363],{15680:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>d,MDXProvider:()=>u,mdx:()=>h,useMDXComponents:()=>s,withMDXComponents:()=>p});var a=n(96540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function m(){return m=Object.assign||function(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var m=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var d=a.createContext({}),p=function(e){return function(t){var n=s(t.components);return a.createElement(e,m({},t,{components:n}))}},s=function(e){var t=a.useContext(d),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},u=function(e){var t=s(e.components);return a.createElement(d.Provider,{value:t},e.children)},c="mdxType",x={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},f=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,m=e.originalType,o=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),p=s(n),u=r,c=p["".concat(o,".").concat(u)]||p[u]||x[u]||m;return n?a.createElement(c,i(i({ref:t},d),{},{components:n})):a.createElement(c,i({ref:t},d))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var m=n.length,o=new Array(m);o[0]=f;var i={};for(var l in t)hasOwnProperty.call(t,l)&&(i[l]=t[l]);i.originalType=e,i[c]="string"==typeof e?e:r,o[1]=i;for(var d=2;d{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>m,metadata:()=>i,toc:()=>d});var a=n(58168),r=(n(96540),n(15680));n(14423);const m={id:"reference",title:"Angle Reference",sidebar_label:"Reference"},o=void 0,i={unversionedId:"angle/reference",id:"angle/reference",title:"Angle Reference",description:"Queries",source:"@site/docs/angle/reference.md",sourceDirName:"angle",slug:"/angle/reference",permalink:"/docs/angle/reference",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/angle/reference.md",tags:[],version:"current",frontMatter:{id:"reference",title:"Angle Reference",sidebar_label:"Reference"},sidebar:"someSidebar",previous:{title:"Debugging",permalink:"/docs/angle/debugging"},next:{title:"Style Guide",permalink:"/docs/angle/style"}},l={},d=[{value:"Queries",id:"queries",level:2},{value:"Statements",id:"statements",level:2},{value:"Names",id:"names",level:2},{value:"Term",id:"term",level:2},{value:"Primitives",id:"primitives",level:2}],p={toc:d},s="wrapper";function u(e){let{components:t,...n}=e;return(0,r.mdx)(s,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.mdx)("h2",{id:"queries"},"Queries"),(0,r.mdx)("p",null,"A query produces a set of values. At the outermost level, the values\nreturned are always ",(0,r.mdx)("em",{parentName:"p"},"facts"),", which are returned to the client making\nthe query."),(0,r.mdx)("p",null,"In general, a Glean query takes the form:"),(0,r.mdx)("p",null,(0,r.mdx)("em",{parentName:"p"},"query")," ::= ","[ ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"where")," ]"," ",(0,r.mdx)("em",{parentName:"p"},"statement\u2080")," ; ...; ",(0,r.mdx)("em",{parentName:"p"},"statement\u2099")),(0,r.mdx)("p",null,"You can think of this declaratively, as in"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"For each substitution of the variables in the query such that ",(0,r.mdx)("em",{parentName:"p"},"statement\u2080"),"..",(0,r.mdx)("em",{parentName:"p"},"statement\u2099")," holds, produce the value of ",(0,r.mdx)("em",{parentName:"p"},"term"))),(0,r.mdx)("p",null,"Or, we can think of it more operationally, which helps with query optimisation:"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"for each value of ",(0,r.mdx)("em",{parentName:"p"},"statement\u2080"),(0,r.mdx)("br",null),"\n...",(0,r.mdx)("br",null),"\nfor each value of ",(0,r.mdx)("em",{parentName:"p"},"statement\u2099"),(0,r.mdx)("br",null),"\nproduce the value of ",(0,r.mdx)("em",{parentName:"p"},"term"))),(0,r.mdx)("p",null,"If ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"where")," is omitted, then the query produces the values of the final statement. For example, a query ",(0,r.mdx)("inlineCode",{parentName:"p"},'src.File "foo/bar"')," is equivalent to ",(0,r.mdx)("inlineCode",{parentName:"p"},'F where F = src.File "foo/bar"'),"."),(0,r.mdx)("p",null,"Note that a query corresponds to a nested loop, where ",(0,r.mdx)("em",{parentName:"p"},"statement\u2080")," is the outermost loop, and ",(0,r.mdx)("em",{parentName:"p"},"statement\u2099")," is the innermost. The ordering of the statements can therefore have a significant effect on performance."),(0,r.mdx)("h2",{id:"statements"},"Statements"),(0,r.mdx)("p",null,(0,r.mdx)("em",{parentName:"p"},"statement")," ::= ","[ ",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"=")," ]"," ",(0,r.mdx)("em",{parentName:"p"},"term\u2082")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"match all values of ",(0,r.mdx)("strong",{parentName:"p"},"term\u2081")," against all values of ",(0,r.mdx)("strong",{parentName:"p"},"term\u2082"))),(0,r.mdx)("p",null,"The order is mostly irrelevant; ",(0,r.mdx)("inlineCode",{parentName:"p"},"A = B")," is equivalent to ",(0,r.mdx)("inlineCode",{parentName:"p"},"B = A"),", except that type inference works by inferring the right-hand-side before checking the left-hand-side so this may influence which order you want. You can also use a type signature (",(0,r.mdx)("inlineCode",{parentName:"p"},"A = B : type"),") to help the type checker."),(0,r.mdx)("h2",{id:"names"},"Names"),(0,r.mdx)("p",null,"Glean uses the following classes of names:"),(0,r.mdx)("ul",null,(0,r.mdx)("li",{parentName:"ul"},"A ",(0,r.mdx)("em",{parentName:"li"},"schema name"),", e.g. ",(0,r.mdx)("inlineCode",{parentName:"li"},"example.schema"),", of the form ",(0,r.mdx)("em",{parentName:"li"},"name"),"[.",(0,r.mdx)("em",{parentName:"li"},"name"),"]","*. By convention, the components of a schema name begin with a lower-case letter."),(0,r.mdx)("li",{parentName:"ul"},"A ",(0,r.mdx)("em",{parentName:"li"},"predicate name"),", e.g. ",(0,r.mdx)("inlineCode",{parentName:"li"},"example.schema.Predicate.1")," of the form ",(0,r.mdx)("em",{parentName:"li"},"schema"),".",(0,r.mdx)("em",{parentName:"li"},"predicate"),"[.",(0,r.mdx)("em",{parentName:"li"},"version"),"]",". By convention, ",(0,r.mdx)("em",{parentName:"li"},"predicate")," begins with an upper-case letter. The version can often be omitted, in which case it defaults depending on the context: in a query it defaults to the most recent version, in a schema there is always only one version of a predicate visible in any given scope."),(0,r.mdx)("li",{parentName:"ul"},"A ",(0,r.mdx)("em",{parentName:"li"},"field name"),", e.g. ",(0,r.mdx)("inlineCode",{parentName:"li"},"declaration"),", used to identify fields of a record, or alternatives of a sum type or enumeration. A field name ",(0,r.mdx)("strong",{parentName:"li"},"must begin with a lower-case letter"),"."),(0,r.mdx)("li",{parentName:"ul"},"A ",(0,r.mdx)("em",{parentName:"li"},"variable"),", e.g. ",(0,r.mdx)("inlineCode",{parentName:"li"},"X"),". Variables ",(0,r.mdx)("strong",{parentName:"li"},"must begin with an upper-case letter")," to distinguish them from field names.")),(0,r.mdx)("p",null,"There is a set of reserved words that can't be used for names. Mostly this is because those words would clash with reserved keywords in code that we generate from the schema, and we don't want to have to do any automatic translation of names that might be confusing. Typically the convention for avoiding these reserved words is to add an underscore to the name, e.g. ",(0,r.mdx)("inlineCode",{parentName:"p"},"class_"),"."),(0,r.mdx)("h2",{id:"term"},"Term"),(0,r.mdx)("p",null,"A term may be fully defined, like ",(0,r.mdx)("inlineCode",{parentName:"p"},"{ true, 123 }")," (a value that we could insert in the database), or it can be partially defined, like ",(0,r.mdx)("inlineCode",{parentName:"p"},'{ A, "b", _ }'),"."),(0,r.mdx)("p",null,"A term is often matched against something that will instantiate its unknown variables. For example, in ",(0,r.mdx)("inlineCode",{parentName:"p"},"cxx.Name X"),", we're instantitating the variable ",(0,r.mdx)("inlineCode",{parentName:"p"},"X")," to each of the keys of the predicate ",(0,r.mdx)("inlineCode",{parentName:"p"},"cxx.Name"),"."),(0,r.mdx)("p",null,"Ultimately the result of a query must be terms that are fully defined, though. If this isn't the case, Glean's query engine will report an error. For example, a query like ",(0,r.mdx)("inlineCode",{parentName:"p"},"X where 123")," doesn't make sense, because we haven't matched ",(0,r.mdx)("inlineCode",{parentName:"p"},"X")," with anything."),(0,r.mdx)("p",null,"Terms have the following forms:"),(0,r.mdx)("p",null,(0,r.mdx)("em",{parentName:"p"},"term")," ::=",(0,r.mdx)("br",null),"\n","\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"variable")," ",(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"A ",(0,r.mdx)("strong",{parentName:"p"},"variable")," names the terms that match at this position in the query. The variable can be mentioned elsewhere in the query; it doesn't usually make sense for a variable to be mentioned only once, since then you might as well just use a wildcard, see below.")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"_"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"A wildcard; matches anything")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"never"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"A pattern that always fails to match.")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"predicate"),"\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ","[ ",(0,r.mdx)("inlineCode",{parentName:"p"},"->")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ]"," ",(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"All the facts of ",(0,r.mdx)("strong",{parentName:"p"},"predicate")," with keys that match the first ",(0,r.mdx)("strong",{parentName:"p"},"term")," (and values that match the second ",(0,r.mdx)("strong",{parentName:"p"},"term")," if appropriate)")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"(")," ",(0,r.mdx)("em",{parentName:"p"},"query")," ",(0,r.mdx)("inlineCode",{parentName:"p"},")")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"All the values of ",(0,r.mdx)("strong",{parentName:"p"},"query"),". Note in particular that ",(0,r.mdx)("strong",{parentName:"p"},"query")," can just be a simple term, but it can also be something like ",(0,r.mdx)("strong",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"where")," ",(0,r.mdx)("strong",{parentName:"p"},"statements"),".")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"[..]")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"All the elements of the array ",(0,r.mdx)("strong",{parentName:"p"},"term"))),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"|")," ",(0,r.mdx)("em",{parentName:"p"},"term\u2082")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"When used as a pattern, matches ",(0,r.mdx)("strong",{parentName:"p"},"term\u2081")," or ",(0,r.mdx)("strong",{parentName:"p"},"term\u2082"),". When used as an expression, generates all values of ",(0,r.mdx)("strong",{parentName:"p"},"term\u2081")," and all values of ",(0,r.mdx)("strong",{parentName:"p"},"term\u2082"),".")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Note: variables mentioned in ",(0,r.mdx)("strong",{parentName:"p"},"term\u2081")," and ",(0,r.mdx)("strong",{parentName:"p"},"term\u2082")," are local to those terms, and may have different types, but only if the variable is not mentioned elsewhere.")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"elements")," ",(0,r.mdx)("em",{parentName:"p"},"term")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"All the elements of the set ",(0,r.mdx)("strong",{parentName:"p"},"term"))),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"all")," ",(0,r.mdx)("em",{parentName:"p"},"query")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Construct a set of all the results of ",(0,r.mdx)("strong",{parentName:"p"},"query"),".")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"!")," ",(0,r.mdx)("em",{parentName:"p"},"term")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"The negation of a term. Fails if the term matches anything and succeeds otherwise.")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"if")," ",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"then")," ",(0,r.mdx)("em",{parentName:"p"},"term\u2082")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"else")," ",(0,r.mdx)("em",{parentName:"p"},"term\u2083")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"If ",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," has matches the ",(0,r.mdx)("inlineCode",{parentName:"p"},"then")," branch is taken and ",(0,r.mdx)("em",{parentName:"p"},"term\u2083")," is never matched against. If ",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," has no matches then the ",(0,r.mdx)("inlineCode",{parentName:"p"},"else")," branch is taken and ",(0,r.mdx)("em",{parentName:"p"},"term\u2082")," is never matched against.")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"[0-9]","+"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"a number matches a value of type ",(0,r.mdx)("inlineCode",{parentName:"p"},"nat")," or ",(0,r.mdx)("inlineCode",{parentName:"p"},"byte"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"string"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"a string matches a value of type ",(0,r.mdx)("inlineCode",{parentName:"p"},"string"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"string")," ",(0,r.mdx)("inlineCode",{parentName:"p"},".."),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches strings with the given prefix")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"string")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"..")," ",(0,r.mdx)("em",{parentName:"p"},"term"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a prefix and the rest of the string")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"{")," ",(0,r.mdx)("em",{parentName:"p"},"field")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"=")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},",")," ... ",(0,r.mdx)("inlineCode",{parentName:"p"},"}"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a record with the given fields")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"{")," ",(0,r.mdx)("em",{parentName:"p"},"field")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"=")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"}")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a sum type with an alternative ",(0,r.mdx)("strong",{parentName:"p"},"field"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"field")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"when matching a sum type, shorthand for ",(0,r.mdx)("inlineCode",{parentName:"p"},"{")," ",(0,r.mdx)("em",{parentName:"p"},"field")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"= _ }"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"enumerator")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches an value of an enumerated type")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"{ just =")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"}"),(0,r.mdx)("br",null),"\n","\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"nothing")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a ",(0,r.mdx)("inlineCode",{parentName:"p"},"maybe")," type")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"true"),(0,r.mdx)("br",null),"\n","\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"false")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a ",(0,r.mdx)("inlineCode",{parentName:"p"},"boolean"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"term"),"\xa0",":","\xa0",(0,r.mdx)("em",{parentName:"p"},"type"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"(a ",(0,r.mdx)("em",{parentName:"p"},"type signature"),") interpret ",(0,r.mdx)("strong",{parentName:"p"},"term")," as having type ",(0,r.mdx)("strong",{parentName:"p"},"type"),", where ",(0,r.mdx)("strong",{parentName:"p"},"type")," is any valid Angle type.")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"$")," ","[0-9]","+",(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a literal fact ID. The only reason to use these would be if you did a previous query, extracted some fact IDs, and want to do a subsequent query incorporating them. Literal fact IDs are not allowed in derived predicates (it wouldn't make any sense).")),(0,r.mdx)("h2",{id:"primitives"},"Primitives"),(0,r.mdx)("p",null,"Angle supports a few primitive operations. The argument(s) to a primitive operation must always be fully defined; they cannot be patterns or wildcards."),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"prim.toLower")," (S : string) : string"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Converts its string argument to lower case")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"prim.length")," (A : ","[_]",") : nat"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Equal to the number of elements in its array argument")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},">")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null),"\n","\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},">=")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null),"\n","\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"<")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null),"\n","\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"<=")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null),"\n","\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"!==")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Standard numerical comparisons. These work on values of type ",(0,r.mdx)("inlineCode",{parentName:"p"},"nat")," only, and they have value ",(0,r.mdx)("inlineCode",{parentName:"p"},"{}")," if the comparison succeeds, otherwise they fail (in the same way as a predicate match fails if there are no facts that match the pattern).")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"!=")," ",(0,r.mdx)("em",{parentName:"p"},"term")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Standard comparison between two terms of any type. It has a value of ",(0,r.mdx)("inlineCode",{parentName:"p"},"{}")," if the comparison succeeds, otherwise it fails in the same way as a predicate match fails if there are no facts that match the pattern.")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"zip")," (A : ","[a]",") (B : ","[b]",") : ","[{a,b}]"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Takes two arrays and zips them together pairwise into a new array of tuples.\nIf the arrays have different length, the result has the same length as the shorter input array.")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"concat")," (A : ","[a]",") (B : ","[a]",") : ","[a]"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Concatenates two arrays together")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"reverse")," (S : string) : string"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Reverses a string")))}u.isMDXComponent=!0},80510:function(e,t,n){var a=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getSpecInfo=void 0;const r=n(88266);t.getSpecInfo=function(e){return a(this,void 0,void 0,(function*(){return yield r.call({module:"bloks",api:"getSpecInfo",args:{styleId:e}})}))}},88266:function(e,t){var n=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.call=void 0;let a=!1,r=0;const m={};t.call=function(e){return n(this,void 0,void 0,(function*(){if("staticdocs.thefacebook.com"!==window.location.hostname&&"localhost"!==window.location.hostname)return Promise.reject(new Error("Not running on static docs"));a||(a=!0,window.addEventListener("message",(e=>{if("static-docs-bridge-response"!==e.data.event)return;const t=e.data.id;t in m||console.error(`Recieved response for id: ${t} with no matching receiver`),"response"in e.data?m[t].resolve(e.data.response):m[t].reject(new Error(e.data.error)),delete m[t]})));const t=r++,n=new Promise(((e,n)=>{m[t]={resolve:e,reject:n}})),o={event:"static-docs-bridge-call",id:t,module:e.module,api:e.api,args:e.args},i="localhost"===window.location.hostname?"*":"https://www.internalfb.com";return window.parent.postMessage(o,i),n}))}},70680:function(e,t,n){var a=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.reportFeatureUsage=t.reportContentCopied=void 0;const r=n(88266);t.reportContentCopied=function(e){return a(this,void 0,void 0,(function*(){const{textContent:t}=e;try{yield r.call({module:"feedback",api:"reportContentCopied",args:{textContent:t}})}catch(n){}}))},t.reportFeatureUsage=function(e){return a(this,void 0,void 0,(function*(){const{featureName:t,id:n}=e;console.log("used feature");try{yield r.call({module:"feedback",api:"reportFeatureUsage",args:{featureName:t,id:n}})}catch(a){}}))}},14423:function(e,t,n){var a=this&&this.__createBinding||(Object.create?function(e,t,n,a){void 0===a&&(a=n),Object.defineProperty(e,a,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,a){void 0===a&&(a=n),e[a]=t[n]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),m=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&a(t,e,n);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.OssOnly=t.FbInternalOnly=t.getEphemeralDiffNumber=t.hasEphemeralDiffNumber=t.isInternal=t.validateFbContentArgs=t.fbInternalOnly=t.fbContent=t.inpageeditor=t.feedback=t.uidocs=t.bloks=void 0,t.bloks=m(n(80510)),t.uidocs=m(n(3730)),t.feedback=m(n(70680)),t.inpageeditor=m(n(45458));const o=["internal","external"];function i(e){return d(e),p()?"internal"in e?l(e.internal):[]:"external"in e?l(e.external):[]}function l(e){return"function"==typeof e?e():e}function d(e){if("object"!=typeof e)throw new Error(`fbContent() args must be an object containing keys: ${o}. Instead got ${e}`);if(!Object.keys(e).find((e=>o.find((t=>t===e)))))throw new Error(`No valid args found in ${JSON.stringify(e)}. Accepted keys: ${o}`);const t=Object.keys(e).filter((e=>!o.find((t=>t===e))));if(t.length>0)throw new Error(`Unexpected keys ${t} found in fbContent() args. Accepted keys: ${o}`)}function p(){try{return Boolean(!1)}catch(e){return console.log("process.env.FB_INTERNAL couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),!1}}function s(){try{return null}catch(e){return console.log("process.env.PHABRICATOR_DIFF_NUMBER couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),null}}t.fbContent=i,t.fbInternalOnly=function(e){return i({internal:e})},t.validateFbContentArgs=d,t.isInternal=p,t.hasEphemeralDiffNumber=function(){return Boolean(s())},t.getEphemeralDiffNumber=s,t.FbInternalOnly=function(e){return p()?e.children:null},t.OssOnly=function(e){return p()?null:e.children}},45458:function(e,t,n){var a=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.submitDiff=void 0;const r=n(88266);t.submitDiff=function(e){return a(this,void 0,void 0,(function*(){const{file_path:t,new_content:n,project_name:a,diff_number:m}=e;try{return yield r.call({module:"inpageeditor",api:"createPhabricatorDiffApi",args:{file_path:t,new_content:n,project_name:a,diff_number:m}})}catch(o){throw new Error(`Error occurred while trying to submit diff. Stack trace: ${o}`)}}))}},3730:function(e,t,n){var a=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getApi=t.docsets=void 0;const r=n(88266);t.docsets={BLOKS_CORE:"887372105406659"},t.getApi=function(e){return a(this,void 0,void 0,(function*(){const{name:t,framework:n,docset:a}=e;return yield r.call({module:"uidocs",api:"getApi",args:{name:t,framework:n,docset:a}})}))}}}]); \ No newline at end of file diff --git a/assets/js/3e65d5b4.f6fca71e.js b/assets/js/3e65d5b4.f6fca71e.js deleted file mode 100644 index 61e689c0d..000000000 --- a/assets/js/3e65d5b4.f6fca71e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[4363],{15680:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>d,MDXProvider:()=>u,mdx:()=>h,useMDXComponents:()=>s,withMDXComponents:()=>p});var a=n(96540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function m(){return m=Object.assign||function(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var m=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var d=a.createContext({}),p=function(e){return function(t){var n=s(t.components);return a.createElement(e,m({},t,{components:n}))}},s=function(e){var t=a.useContext(d),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},u=function(e){var t=s(e.components);return a.createElement(d.Provider,{value:t},e.children)},c="mdxType",x={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},f=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,m=e.originalType,o=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),p=s(n),u=r,c=p["".concat(o,".").concat(u)]||p[u]||x[u]||m;return n?a.createElement(c,i(i({ref:t},d),{},{components:n})):a.createElement(c,i({ref:t},d))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var m=n.length,o=new Array(m);o[0]=f;var i={};for(var l in t)hasOwnProperty.call(t,l)&&(i[l]=t[l]);i.originalType=e,i[c]="string"==typeof e?e:r,o[1]=i;for(var d=2;d{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>m,metadata:()=>i,toc:()=>d});var a=n(58168),r=(n(96540),n(15680));n(14423);const m={id:"reference",title:"Angle Reference",sidebar_label:"Reference"},o=void 0,i={unversionedId:"angle/reference",id:"angle/reference",title:"Angle Reference",description:"Queries",source:"@site/docs/angle/reference.md",sourceDirName:"angle",slug:"/angle/reference",permalink:"/docs/angle/reference",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/angle/reference.md",tags:[],version:"current",frontMatter:{id:"reference",title:"Angle Reference",sidebar_label:"Reference"},sidebar:"someSidebar",previous:{title:"Debugging",permalink:"/docs/angle/debugging"},next:{title:"Style Guide",permalink:"/docs/angle/style"}},l={},d=[{value:"Queries",id:"queries",level:2},{value:"Statements",id:"statements",level:2},{value:"Names",id:"names",level:2},{value:"Term",id:"term",level:2},{value:"Primitives",id:"primitives",level:2}],p={toc:d},s="wrapper";function u(e){let{components:t,...n}=e;return(0,r.mdx)(s,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.mdx)("h2",{id:"queries"},"Queries"),(0,r.mdx)("p",null,"A query produces a set of values. At the outermost level, the values\nreturned are always ",(0,r.mdx)("em",{parentName:"p"},"facts"),", which are returned to the client making\nthe query."),(0,r.mdx)("p",null,"In general, a Glean query takes the form:"),(0,r.mdx)("p",null,(0,r.mdx)("em",{parentName:"p"},"query")," ::= ","[ ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"where")," ]"," ",(0,r.mdx)("em",{parentName:"p"},"statement\u2080")," ; ...; ",(0,r.mdx)("em",{parentName:"p"},"statement\u2099")),(0,r.mdx)("p",null,"You can think of this declaratively, as in"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"For each substitution of the variables in the query such that ",(0,r.mdx)("em",{parentName:"p"},"statement\u2080"),"..",(0,r.mdx)("em",{parentName:"p"},"statement\u2099")," holds, produce the value of ",(0,r.mdx)("em",{parentName:"p"},"term"))),(0,r.mdx)("p",null,"Or, we can think of it more operationally, which helps with query optimisation:"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"for each value of ",(0,r.mdx)("em",{parentName:"p"},"statement\u2080"),(0,r.mdx)("br",null),"\n...",(0,r.mdx)("br",null),"\nfor each value of ",(0,r.mdx)("em",{parentName:"p"},"statement\u2099"),(0,r.mdx)("br",null),"\nproduce the value of ",(0,r.mdx)("em",{parentName:"p"},"term"))),(0,r.mdx)("p",null,"If ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"where")," is omitted, then the query produces the values of the final statement. For example, a query ",(0,r.mdx)("inlineCode",{parentName:"p"},'src.File "foo/bar"')," is equivalent to ",(0,r.mdx)("inlineCode",{parentName:"p"},'F where F = src.File "foo/bar"'),"."),(0,r.mdx)("p",null,"Note that a query corresponds to a nested loop, where ",(0,r.mdx)("em",{parentName:"p"},"statement\u2080")," is the outermost loop, and ",(0,r.mdx)("em",{parentName:"p"},"statement\u2099")," is the innermost. The ordering of the statements can therefore have a significant effect on performance."),(0,r.mdx)("h2",{id:"statements"},"Statements"),(0,r.mdx)("p",null,(0,r.mdx)("em",{parentName:"p"},"statement")," ::= ","[ ",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"=")," ]"," ",(0,r.mdx)("em",{parentName:"p"},"term\u2082")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"match all values of ",(0,r.mdx)("strong",{parentName:"p"},"term\u2081")," against all values of ",(0,r.mdx)("strong",{parentName:"p"},"term\u2082"))),(0,r.mdx)("p",null,"The order is mostly irrelevant; ",(0,r.mdx)("inlineCode",{parentName:"p"},"A = B")," is equivalent to ",(0,r.mdx)("inlineCode",{parentName:"p"},"B = A"),", except that type inference works by inferring the right-hand-side before checking the left-hand-side so this may influence which order you want. You can also use a type signature (",(0,r.mdx)("inlineCode",{parentName:"p"},"A = B : type"),") to help the type checker."),(0,r.mdx)("h2",{id:"names"},"Names"),(0,r.mdx)("p",null,"Glean uses the following classes of names:"),(0,r.mdx)("ul",null,(0,r.mdx)("li",{parentName:"ul"},"A ",(0,r.mdx)("em",{parentName:"li"},"schema name"),", e.g. ",(0,r.mdx)("inlineCode",{parentName:"li"},"example.schema"),", of the form ",(0,r.mdx)("em",{parentName:"li"},"name"),"[.",(0,r.mdx)("em",{parentName:"li"},"name"),"]","*. By convention, the components of a schema name begin with a lower-case letter."),(0,r.mdx)("li",{parentName:"ul"},"A ",(0,r.mdx)("em",{parentName:"li"},"predicate name"),", e.g. ",(0,r.mdx)("inlineCode",{parentName:"li"},"example.schema.Predicate.1")," of the form ",(0,r.mdx)("em",{parentName:"li"},"schema"),".",(0,r.mdx)("em",{parentName:"li"},"predicate"),"[.",(0,r.mdx)("em",{parentName:"li"},"version"),"]",". By convention, ",(0,r.mdx)("em",{parentName:"li"},"predicate")," begins with an upper-case letter. The version can often be omitted, in which case it defaults depending on the context: in a query it defaults to the most recent version, in a schema there is always only one version of a predicate visible in any given scope."),(0,r.mdx)("li",{parentName:"ul"},"A ",(0,r.mdx)("em",{parentName:"li"},"field name"),", e.g. ",(0,r.mdx)("inlineCode",{parentName:"li"},"declaration"),", used to identify fields of a record, or alternatives of a sum type or enumeration. A field name ",(0,r.mdx)("strong",{parentName:"li"},"must begin with a lower-case letter"),"."),(0,r.mdx)("li",{parentName:"ul"},"A ",(0,r.mdx)("em",{parentName:"li"},"variable"),", e.g. ",(0,r.mdx)("inlineCode",{parentName:"li"},"X"),". Variables ",(0,r.mdx)("strong",{parentName:"li"},"must begin with an upper-case letter")," to distinguish them from field names.")),(0,r.mdx)("p",null,"There is a set of reserved words that can't be used for names. Mostly this is because those words would clash with reserved keywords in code that we generate from the schema, and we don't want to have to do any automatic translation of names that might be confusing. Typically the convention for avoiding these reserved words is to add an underscore to the name, e.g. ",(0,r.mdx)("inlineCode",{parentName:"p"},"class_"),"."),(0,r.mdx)("h2",{id:"term"},"Term"),(0,r.mdx)("p",null,"A term may be fully defined, like ",(0,r.mdx)("inlineCode",{parentName:"p"},"{ true, 123 }")," (a value that we could insert in the database), or it can be partially defined, like ",(0,r.mdx)("inlineCode",{parentName:"p"},'{ A, "b", _ }'),"."),(0,r.mdx)("p",null,"A term is often matched against something that will instantiate its unknown variables. For example, in ",(0,r.mdx)("inlineCode",{parentName:"p"},"cxx.Name X"),", we're instantitating the variable ",(0,r.mdx)("inlineCode",{parentName:"p"},"X")," to each of the keys of the predicate ",(0,r.mdx)("inlineCode",{parentName:"p"},"cxx.Name"),"."),(0,r.mdx)("p",null,"Ultimately the result of a query must be terms that are fully defined, though. If this isn't the case, Glean's query engine will report an error. For example, a query like ",(0,r.mdx)("inlineCode",{parentName:"p"},"X where 123")," doesn't make sense, because we haven't matched ",(0,r.mdx)("inlineCode",{parentName:"p"},"X")," with anything."),(0,r.mdx)("p",null,"Terms have the following forms:"),(0,r.mdx)("p",null,(0,r.mdx)("em",{parentName:"p"},"term")," ::=",(0,r.mdx)("br",null),"\n","\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"variable")," ",(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"A ",(0,r.mdx)("strong",{parentName:"p"},"variable")," names the terms that match at this position in the query. The variable can be mentioned elsewhere in the query; it doesn't usually make sense for a variable to be mentioned only once, since then you might as well just use a wildcard, see below.")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"_"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"A wildcard; matches anything")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"never"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"A pattern that always fails to match.")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"predicate"),"\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ","[ ",(0,r.mdx)("inlineCode",{parentName:"p"},"->")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ]"," ",(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"All the facts of ",(0,r.mdx)("strong",{parentName:"p"},"predicate")," with keys that match the first ",(0,r.mdx)("strong",{parentName:"p"},"term")," (and values that match the second ",(0,r.mdx)("strong",{parentName:"p"},"term")," if appropriate)")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"(")," ",(0,r.mdx)("em",{parentName:"p"},"query")," ",(0,r.mdx)("inlineCode",{parentName:"p"},")")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"All the values of ",(0,r.mdx)("strong",{parentName:"p"},"query"),". Note in particular that ",(0,r.mdx)("strong",{parentName:"p"},"query")," can just be a simple term, but it can also be something like ",(0,r.mdx)("strong",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"where")," ",(0,r.mdx)("strong",{parentName:"p"},"statements"),".")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"[..]")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"All the elements of the array ",(0,r.mdx)("strong",{parentName:"p"},"term"))),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"|")," ",(0,r.mdx)("em",{parentName:"p"},"term\u2082")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"When used as a pattern, matches ",(0,r.mdx)("strong",{parentName:"p"},"term\u2081")," or ",(0,r.mdx)("strong",{parentName:"p"},"term\u2082"),". When used as an expression, generates all values of ",(0,r.mdx)("strong",{parentName:"p"},"term\u2081")," and all values of ",(0,r.mdx)("strong",{parentName:"p"},"term\u2082"),".")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Note: variables mentioned in ",(0,r.mdx)("strong",{parentName:"p"},"term\u2081")," and ",(0,r.mdx)("strong",{parentName:"p"},"term\u2082")," are local to those terms, and may have different types, but only if the variable is not mentioned elsewhere.")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"!")," ",(0,r.mdx)("em",{parentName:"p"},"term")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"The negation of a term. Fails if the term matches anything and succeeds otherwise.")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"if")," ",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"then")," ",(0,r.mdx)("em",{parentName:"p"},"term\u2082")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"else")," ",(0,r.mdx)("em",{parentName:"p"},"term\u2083")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"If ",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," has matches the ",(0,r.mdx)("inlineCode",{parentName:"p"},"then")," branch is taken and ",(0,r.mdx)("em",{parentName:"p"},"term\u2083")," is never matched against. If ",(0,r.mdx)("em",{parentName:"p"},"term\u2081")," has no matches then the ",(0,r.mdx)("inlineCode",{parentName:"p"},"else")," branch is taken and ",(0,r.mdx)("em",{parentName:"p"},"term\u2082")," is never matched against.")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"[0-9]","+"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"a number matches a value of type ",(0,r.mdx)("inlineCode",{parentName:"p"},"nat")," or ",(0,r.mdx)("inlineCode",{parentName:"p"},"byte"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"string"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"a string matches a value of type ",(0,r.mdx)("inlineCode",{parentName:"p"},"string"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"string")," ",(0,r.mdx)("inlineCode",{parentName:"p"},".."),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches strings with the given prefix")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"string")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"..")," ",(0,r.mdx)("em",{parentName:"p"},"term"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a prefix and the rest of the string")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"{")," ",(0,r.mdx)("em",{parentName:"p"},"field")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"=")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},",")," ... ",(0,r.mdx)("inlineCode",{parentName:"p"},"}"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a record with the given fields")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"{")," ",(0,r.mdx)("em",{parentName:"p"},"field")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"=")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"}")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a sum type with an alternative ",(0,r.mdx)("strong",{parentName:"p"},"field"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"field")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"when matching a sum type, shorthand for ",(0,r.mdx)("inlineCode",{parentName:"p"},"{")," ",(0,r.mdx)("em",{parentName:"p"},"field")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"= _ }"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"enumerator")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches an value of an enumerated type")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"{ just =")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"}"),(0,r.mdx)("br",null),"\n","\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"nothing")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a ",(0,r.mdx)("inlineCode",{parentName:"p"},"maybe")," type")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"true"),(0,r.mdx)("br",null),"\n","\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"false")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a ",(0,r.mdx)("inlineCode",{parentName:"p"},"boolean"))),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("em",{parentName:"p"},"term"),"\xa0",":","\xa0",(0,r.mdx)("em",{parentName:"p"},"type"),(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"(a ",(0,r.mdx)("em",{parentName:"p"},"type signature"),") interpret ",(0,r.mdx)("strong",{parentName:"p"},"term")," as having type ",(0,r.mdx)("strong",{parentName:"p"},"type"),", where ",(0,r.mdx)("strong",{parentName:"p"},"type")," is any valid Angle type.")),(0,r.mdx)("p",null,"\xa0","\xa0"," ",(0,r.mdx)("inlineCode",{parentName:"p"},"$")," ","[0-9]","+",(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"matches a literal fact ID. The only reason to use these would be if you did a previous query, extracted some fact IDs, and want to do a subsequent query incorporating them. Literal fact IDs are not allowed in derived predicates (it wouldn't make any sense).")),(0,r.mdx)("h2",{id:"primitives"},"Primitives"),(0,r.mdx)("p",null,"Angle supports a few primitive operations. The argument(s) to a primitive operation must always be fully defined; they cannot be patterns or wildcards."),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"prim.toLower")," (S : string) : string"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Converts its string argument to lower case")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("inlineCode",{parentName:"p"},"prim.length")," (A : ","[_]",") : nat"),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Equal to the number of elements in its array argument")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},">")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null),"\n","\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},">=")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null),"\n","\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"<")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null),"\n","\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"<=")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null),"\n","\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"!==")," ",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("br",null)),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Standard numerical comparisons. These work on values of type ",(0,r.mdx)("inlineCode",{parentName:"p"},"nat")," only, and they have value ",(0,r.mdx)("inlineCode",{parentName:"p"},"{}")," if the comparison succeeds, otherwise they fail (in the same way as a predicate match fails if there are no facts that match the pattern).")),(0,r.mdx)("p",null,"\xa0","\xa0",(0,r.mdx)("em",{parentName:"p"},"term")," ",(0,r.mdx)("inlineCode",{parentName:"p"},"!=")," ",(0,r.mdx)("em",{parentName:"p"},"term")),(0,r.mdx)("blockquote",null,(0,r.mdx)("p",{parentName:"blockquote"},"Standard comparison between two terms of any type. It has a value of ",(0,r.mdx)("inlineCode",{parentName:"p"},"{}")," if the comparison succeeds, otherwise it fails in the same way as a predicate match fails if there are no facts that match the pattern.")))}u.isMDXComponent=!0},80510:function(e,t,n){var a=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getSpecInfo=void 0;const r=n(88266);t.getSpecInfo=function(e){return a(this,void 0,void 0,(function*(){return yield r.call({module:"bloks",api:"getSpecInfo",args:{styleId:e}})}))}},88266:function(e,t){var n=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.call=void 0;let a=!1,r=0;const m={};t.call=function(e){return n(this,void 0,void 0,(function*(){if("staticdocs.thefacebook.com"!==window.location.hostname&&"localhost"!==window.location.hostname)return Promise.reject(new Error("Not running on static docs"));a||(a=!0,window.addEventListener("message",(e=>{if("static-docs-bridge-response"!==e.data.event)return;const t=e.data.id;t in m||console.error(`Recieved response for id: ${t} with no matching receiver`),"response"in e.data?m[t].resolve(e.data.response):m[t].reject(new Error(e.data.error)),delete m[t]})));const t=r++,n=new Promise(((e,n)=>{m[t]={resolve:e,reject:n}})),o={event:"static-docs-bridge-call",id:t,module:e.module,api:e.api,args:e.args},i="localhost"===window.location.hostname?"*":"https://www.internalfb.com";return window.parent.postMessage(o,i),n}))}},70680:function(e,t,n){var a=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.reportFeatureUsage=t.reportContentCopied=void 0;const r=n(88266);t.reportContentCopied=function(e){return a(this,void 0,void 0,(function*(){const{textContent:t}=e;try{yield r.call({module:"feedback",api:"reportContentCopied",args:{textContent:t}})}catch(n){}}))},t.reportFeatureUsage=function(e){return a(this,void 0,void 0,(function*(){const{featureName:t,id:n}=e;console.log("used feature");try{yield r.call({module:"feedback",api:"reportFeatureUsage",args:{featureName:t,id:n}})}catch(a){}}))}},14423:function(e,t,n){var a=this&&this.__createBinding||(Object.create?function(e,t,n,a){void 0===a&&(a=n),Object.defineProperty(e,a,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,a){void 0===a&&(a=n),e[a]=t[n]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),m=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&a(t,e,n);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.OssOnly=t.FbInternalOnly=t.getEphemeralDiffNumber=t.hasEphemeralDiffNumber=t.isInternal=t.validateFbContentArgs=t.fbInternalOnly=t.fbContent=t.inpageeditor=t.feedback=t.uidocs=t.bloks=void 0,t.bloks=m(n(80510)),t.uidocs=m(n(3730)),t.feedback=m(n(70680)),t.inpageeditor=m(n(45458));const o=["internal","external"];function i(e){return d(e),p()?"internal"in e?l(e.internal):[]:"external"in e?l(e.external):[]}function l(e){return"function"==typeof e?e():e}function d(e){if("object"!=typeof e)throw new Error(`fbContent() args must be an object containing keys: ${o}. Instead got ${e}`);if(!Object.keys(e).find((e=>o.find((t=>t===e)))))throw new Error(`No valid args found in ${JSON.stringify(e)}. Accepted keys: ${o}`);const t=Object.keys(e).filter((e=>!o.find((t=>t===e))));if(t.length>0)throw new Error(`Unexpected keys ${t} found in fbContent() args. Accepted keys: ${o}`)}function p(){try{return Boolean(!1)}catch(e){return console.log("process.env.FB_INTERNAL couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),!1}}function s(){try{return null}catch(e){return console.log("process.env.PHABRICATOR_DIFF_NUMBER couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),null}}t.fbContent=i,t.fbInternalOnly=function(e){return i({internal:e})},t.validateFbContentArgs=d,t.isInternal=p,t.hasEphemeralDiffNumber=function(){return Boolean(s())},t.getEphemeralDiffNumber=s,t.FbInternalOnly=function(e){return p()?e.children:null},t.OssOnly=function(e){return p()?null:e.children}},45458:function(e,t,n){var a=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.submitDiff=void 0;const r=n(88266);t.submitDiff=function(e){return a(this,void 0,void 0,(function*(){const{file_path:t,new_content:n,project_name:a,diff_number:m}=e;try{return yield r.call({module:"inpageeditor",api:"createPhabricatorDiffApi",args:{file_path:t,new_content:n,project_name:a,diff_number:m}})}catch(o){throw new Error(`Error occurred while trying to submit diff. Stack trace: ${o}`)}}))}},3730:function(e,t,n){var a=this&&this.__awaiter||function(e,t,n,a){return new(n||(n=Promise))((function(r,m){function o(e){try{l(a.next(e))}catch(t){m(t)}}function i(e){try{l(a.throw(e))}catch(t){m(t)}}function l(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,i)}l((a=a.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getApi=t.docsets=void 0;const r=n(88266);t.docsets={BLOKS_CORE:"887372105406659"},t.getApi=function(e){return a(this,void 0,void 0,(function*(){const{name:t,framework:n,docset:a}=e;return yield r.call({module:"uidocs",api:"getApi",args:{name:t,framework:n,docset:a}})}))}}}]); \ No newline at end of file diff --git a/assets/js/432e378d.206c9773.js b/assets/js/432e378d.206c9773.js deleted file mode 100644 index c7ace4ded..000000000 --- a/assets/js/432e378d.206c9773.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[6533],{15680:(e,n,t)=>{t.r(n),t.d(n,{MDXContext:()=>m,MDXProvider:()=>p,mdx:()=>h,useMDXComponents:()=>c,withMDXComponents:()=>u});var r=t(96540);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(){return i=Object.assign||function(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var m=r.createContext({}),u=function(e){return function(n){var t=c(n.components);return r.createElement(e,i({},n,{components:t}))}},c=function(e){var n=r.useContext(m),t=n;return e&&(t="function"==typeof e?e(n):o(o({},n),e)),t},p=function(e){var n=c(e.components);return r.createElement(m.Provider,{value:n},e.children)},s="mdxType",f={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},x=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,i=e.originalType,d=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),u=c(t),p=a,s=u["".concat(d,".").concat(p)]||u[p]||f[p]||i;return t?r.createElement(s,o(o({ref:n},m),{},{components:t})):r.createElement(s,o({ref:n},m))}));function h(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=t.length,d=new Array(i);d[0]=x;var o={};for(var l in n)hasOwnProperty.call(n,l)&&(o[l]=n[l]);o.originalType=e,o[s]="string"==typeof e?e:a,d[1]=o;for(var m=2;m{t.r(n),t.d(n,{assets:()=>m,contentTitle:()=>o,default:()=>s,frontMatter:()=>d,metadata:()=>l,toc:()=>u});var r=t(58168),a=(t(96540),t(15680)),i=t(14423);const d={id:"thrift",title:"Thrift and JSON",sidebar_label:"Thrift and JSON"},o=void 0,l={unversionedId:"schema/thrift",id:"schema/thrift",title:"Thrift and JSON",description:"The Glean schema is automatically translated into a set of Thrift type",source:"@site/docs/schema/thrift.md",sourceDirName:"schema",slug:"/schema/thrift",permalink:"/docs/schema/thrift",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/schema/thrift.md",tags:[],version:"current",frontMatter:{id:"thrift",title:"Thrift and JSON",sidebar_label:"Thrift and JSON"},sidebar:"someSidebar",previous:{title:"Workflow",permalink:"/docs/schema/workflow"},next:{title:"Design",permalink:"/docs/schema/design"}},m={},u=[],c={toc:u},p="wrapper";function s(e){let{components:n,...t}=e;return(0,a.mdx)(p,(0,r.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"The Glean schema is automatically translated into a set of Thrift type\ndefinitions by the ",(0,a.mdx)("inlineCode",{parentName:"p"},"gen-schema")," tool (see ",(0,a.mdx)("a",{parentName:"p",href:"/docs/schema/workflow"},"Workflow"),").\nThese Thrift definitions can be used to work with Glean data in your\nclient, as native data types in whatever language you're using, either\nfor querying data or for writing facts."),(0,a.mdx)("p",null,"The Thrift types also have a JSON representation, which can be read\nand written directly. When you perform queries in the\n",(0,a.mdx)("a",{parentName:"p",href:"/docs/shell"},"shell"),", the results are printed as JSON-encoded Thrift;\nwhen you ",(0,a.mdx)("a",{parentName:"p",href:"/docs/write"},"write data to Glean")," it can be in the form of\nJSON-encoded Thrift."),(0,a.mdx)(i.FbInternalOnly,{mdxType:"FbInternalOnly"},(0,a.mdx)("p",null,"Facebook internal: the Thrift types for the schema are automatically\ngenerated into\n",(0,a.mdx)("a",{parentName:"p",href:"https://phabricator.intern.facebook.com/diffusion/FBS/browse/master/fbcode/glean/schema"},"fbcode/glean/schema"),", and those files are automatically sync'd to\nwww too.")),(0,a.mdx)("p",null,"The relationship between schema types and Thrift/JSON is given by the following table:"),(0,a.mdx)("table",null,(0,a.mdx)("thead",{parentName:"table"},(0,a.mdx)("tr",{parentName:"thead"},(0,a.mdx)("th",{parentName:"tr",align:null},"Schema type"),(0,a.mdx)("th",{parentName:"tr",align:null},"Thrift type"),(0,a.mdx)("th",{parentName:"tr",align:null},"JSON"))),(0,a.mdx)("tbody",{parentName:"table"},(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"nat")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"Nat")," (",(0,a.mdx)("inlineCode",{parentName:"td"},"i64"),")"),(0,a.mdx)("td",{parentName:"tr",align:null},"123")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"byte")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"Byte")," (",(0,a.mdx)("inlineCode",{parentName:"td"},"i8"),")"),(0,a.mdx)("td",{parentName:"tr",align:null},"123")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"string")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"string")),(0,a.mdx)("td",{parentName:"tr",align:null},'"abc"')),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"bool")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"bool")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"true")," or ",(0,a.mdx)("inlineCode",{parentName:"td"},"false"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"[byte]")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"binary")),(0,a.mdx)("td",{parentName:"tr",align:null},"base-64 encoded string ",(0,a.mdx)("sup",null,"*1"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"[T]")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"list")),(0,a.mdx)("td",{parentName:"tr",align:null},"[...]")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"{"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2081 : T\u2081,"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"...,"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2099 : T\u2099"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"struct Foo {"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"1: T\u2081 f\u2081;"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"n: T\u2099 f\u2099;"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"{"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},'"f\u2081" : q\u2081,'),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},'"f\u2099" : q\u2099'),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"{"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2081 : T\u2081 "),(0,a.mdx)("code",null,"|"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"... "),(0,a.mdx)("code",null,"|"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2099 : T\u2099"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"union Foo {"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"1: T\u2081 f\u2081;"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"n: T\u2099 f\u2099;"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},'{ "f" : t }'),(0,a.mdx)("br",null),"for one of the fields ",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2081"),"..",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2099"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"maybe T")),(0,a.mdx)("td",{parentName:"tr",align:null},"In a record field:",(0,a.mdx)("br",null)," ",(0,a.mdx)("inlineCode",{parentName:"td"},"optional T f")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"f : t"),(0,a.mdx)("br",null)," if the value is present")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"enum {"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"L\u2081"),(0,a.mdx)("code",null,"|"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("code",null,"|"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"L\u2099"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"enum Foo { "),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"L\u2081 = 1,"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"L\u2099 = n"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},"the index of the value,",(0,a.mdx)("br",null)," e.g. 12")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"predicate P : K -> V")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"struct P {"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"1: Id id"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"2: optional K key"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"3: optional V value"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}"),(0,a.mdx)("br",null),"note",(0,a.mdx)("sup",null,"*2")),(0,a.mdx)("td",{parentName:"tr",align:null},"refer to fact N:",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"N")," or ",(0,a.mdx)("inlineCode",{parentName:"td"},'{ "id": N }'),(0,a.mdx)("br",null),"define a fact:",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},'{ "id" : N,'),(0,a.mdx)("br",null),"\xa0","\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},'"key" : t }')," or",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},'{ "key": t }')," or",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},'{ "key": t,'),(0,a.mdx)("br",null),"\xa0","\xa0","\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},'"value" : v }'))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"type N = T")),(0,a.mdx)("td",{parentName:"tr",align:null},"depending on T: ",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"struct N { .. }"),(0,a.mdx)("br",null)," ",(0,a.mdx)("inlineCode",{parentName:"td"},"union N {...}"),(0,a.mdx)("br",null)," ",(0,a.mdx)("inlineCode",{parentName:"td"},"enum N {...}"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"typedef T N;")),(0,a.mdx)("td",{parentName:"tr",align:null},"same as type T")))),(0,a.mdx)("ol",null,(0,a.mdx)("li",{parentName:"ol"},(0,a.mdx)("p",{parentName:"li"},"The Thrift encoding of a binary field in JSON is a base-64-encoded string. However, not all Thrift implementations respect this. At the time of writing, the Python Thrift implementation doesn't base-64-encode binary values. For this reason we provide an option in the Glean Thrift API to disable base-64 encoding for binary if your client doesn't support it. The Glean Shell also uses this option to make it easier to work with binary.")),(0,a.mdx)("li",{parentName:"ol"},(0,a.mdx)("p",{parentName:"li"},"the ",(0,a.mdx)("inlineCode",{parentName:"p"},"key")," is optional - a nested fact may be expanded in place or represented by a reference to the fact ID only. When querying Glean data the query specifies which nested facts should be expanded in the result, and when writing data to Glean using Thrift or JSON, we can optionally specify the value of nested facts inline."))))}s.isMDXComponent=!0},80510:function(e,n,t){var r=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.getSpecInfo=void 0;const a=t(88266);n.getSpecInfo=function(e){return r(this,void 0,void 0,(function*(){return yield a.call({module:"bloks",api:"getSpecInfo",args:{styleId:e}})}))}},88266:function(e,n){var t=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.call=void 0;let r=!1,a=0;const i={};n.call=function(e){return t(this,void 0,void 0,(function*(){if("staticdocs.thefacebook.com"!==window.location.hostname&&"localhost"!==window.location.hostname)return Promise.reject(new Error("Not running on static docs"));r||(r=!0,window.addEventListener("message",(e=>{if("static-docs-bridge-response"!==e.data.event)return;const n=e.data.id;n in i||console.error(`Recieved response for id: ${n} with no matching receiver`),"response"in e.data?i[n].resolve(e.data.response):i[n].reject(new Error(e.data.error)),delete i[n]})));const n=a++,t=new Promise(((e,t)=>{i[n]={resolve:e,reject:t}})),d={event:"static-docs-bridge-call",id:n,module:e.module,api:e.api,args:e.args},o="localhost"===window.location.hostname?"*":"https://www.internalfb.com";return window.parent.postMessage(d,o),t}))}},70680:function(e,n,t){var r=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.reportFeatureUsage=n.reportContentCopied=void 0;const a=t(88266);n.reportContentCopied=function(e){return r(this,void 0,void 0,(function*(){const{textContent:n}=e;try{yield a.call({module:"feedback",api:"reportContentCopied",args:{textContent:n}})}catch(t){}}))},n.reportFeatureUsage=function(e){return r(this,void 0,void 0,(function*(){const{featureName:n,id:t}=e;console.log("used feature");try{yield a.call({module:"feedback",api:"reportFeatureUsage",args:{featureName:n,id:t}})}catch(r){}}))}},14423:function(e,n,t){var r=this&&this.__createBinding||(Object.create?function(e,n,t,r){void 0===r&&(r=t),Object.defineProperty(e,r,{enumerable:!0,get:function(){return n[t]}})}:function(e,n,t,r){void 0===r&&(r=t),e[r]=n[t]}),a=this&&this.__setModuleDefault||(Object.create?function(e,n){Object.defineProperty(e,"default",{enumerable:!0,value:n})}:function(e,n){e.default=n}),i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var n={};if(null!=e)for(var t in e)"default"!==t&&Object.prototype.hasOwnProperty.call(e,t)&&r(n,e,t);return a(n,e),n};Object.defineProperty(n,"__esModule",{value:!0}),n.OssOnly=n.FbInternalOnly=n.getEphemeralDiffNumber=n.hasEphemeralDiffNumber=n.isInternal=n.validateFbContentArgs=n.fbInternalOnly=n.fbContent=n.inpageeditor=n.feedback=n.uidocs=n.bloks=void 0,n.bloks=i(t(80510)),n.uidocs=i(t(3730)),n.feedback=i(t(70680)),n.inpageeditor=i(t(45458));const d=["internal","external"];function o(e){return m(e),u()?"internal"in e?l(e.internal):[]:"external"in e?l(e.external):[]}function l(e){return"function"==typeof e?e():e}function m(e){if("object"!=typeof e)throw new Error(`fbContent() args must be an object containing keys: ${d}. Instead got ${e}`);if(!Object.keys(e).find((e=>d.find((n=>n===e)))))throw new Error(`No valid args found in ${JSON.stringify(e)}. Accepted keys: ${d}`);const n=Object.keys(e).filter((e=>!d.find((n=>n===e))));if(n.length>0)throw new Error(`Unexpected keys ${n} found in fbContent() args. Accepted keys: ${d}`)}function u(){try{return Boolean(!1)}catch(e){return console.log("process.env.FB_INTERNAL couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),!1}}function c(){try{return null}catch(e){return console.log("process.env.PHABRICATOR_DIFF_NUMBER couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),null}}n.fbContent=o,n.fbInternalOnly=function(e){return o({internal:e})},n.validateFbContentArgs=m,n.isInternal=u,n.hasEphemeralDiffNumber=function(){return Boolean(c())},n.getEphemeralDiffNumber=c,n.FbInternalOnly=function(e){return u()?e.children:null},n.OssOnly=function(e){return u()?null:e.children}},45458:function(e,n,t){var r=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.submitDiff=void 0;const a=t(88266);n.submitDiff=function(e){return r(this,void 0,void 0,(function*(){const{file_path:n,new_content:t,project_name:r,diff_number:i}=e;try{return yield a.call({module:"inpageeditor",api:"createPhabricatorDiffApi",args:{file_path:n,new_content:t,project_name:r,diff_number:i}})}catch(d){throw new Error(`Error occurred while trying to submit diff. Stack trace: ${d}`)}}))}},3730:function(e,n,t){var r=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.getApi=n.docsets=void 0;const a=t(88266);n.docsets={BLOKS_CORE:"887372105406659"},n.getApi=function(e){return r(this,void 0,void 0,(function*(){const{name:n,framework:t,docset:r}=e;return yield a.call({module:"uidocs",api:"getApi",args:{name:n,framework:t,docset:r}})}))}}}]); \ No newline at end of file diff --git a/assets/js/432e378d.2c47b22a.js b/assets/js/432e378d.2c47b22a.js new file mode 100644 index 000000000..df3aa9068 --- /dev/null +++ b/assets/js/432e378d.2c47b22a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[6533],{15680:(e,n,t)=>{t.r(n),t.d(n,{MDXContext:()=>m,MDXProvider:()=>p,mdx:()=>h,useMDXComponents:()=>c,withMDXComponents:()=>u});var r=t(96540);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(){return i=Object.assign||function(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var m=r.createContext({}),u=function(e){return function(n){var t=c(n.components);return r.createElement(e,i({},n,{components:t}))}},c=function(e){var n=r.useContext(m),t=n;return e&&(t="function"==typeof e?e(n):o(o({},n),e)),t},p=function(e){var n=c(e.components);return r.createElement(m.Provider,{value:n},e.children)},s="mdxType",f={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},x=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,i=e.originalType,d=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),u=c(t),p=a,s=u["".concat(d,".").concat(p)]||u[p]||f[p]||i;return t?r.createElement(s,o(o({ref:n},m),{},{components:t})):r.createElement(s,o({ref:n},m))}));function h(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=t.length,d=new Array(i);d[0]=x;var o={};for(var l in n)hasOwnProperty.call(n,l)&&(o[l]=n[l]);o.originalType=e,o[s]="string"==typeof e?e:a,d[1]=o;for(var m=2;m{t.r(n),t.d(n,{assets:()=>m,contentTitle:()=>o,default:()=>s,frontMatter:()=>d,metadata:()=>l,toc:()=>u});var r=t(58168),a=(t(96540),t(15680)),i=t(14423);const d={id:"thrift",title:"Thrift and JSON",sidebar_label:"Thrift and JSON"},o=void 0,l={unversionedId:"schema/thrift",id:"schema/thrift",title:"Thrift and JSON",description:"The Glean schema is automatically translated into a set of Thrift type",source:"@site/docs/schema/thrift.md",sourceDirName:"schema",slug:"/schema/thrift",permalink:"/docs/schema/thrift",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/schema/thrift.md",tags:[],version:"current",frontMatter:{id:"thrift",title:"Thrift and JSON",sidebar_label:"Thrift and JSON"},sidebar:"someSidebar",previous:{title:"Workflow",permalink:"/docs/schema/workflow"},next:{title:"Design",permalink:"/docs/schema/design"}},m={},u=[],c={toc:u},p="wrapper";function s(e){let{components:n,...t}=e;return(0,a.mdx)(p,(0,r.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"The Glean schema is automatically translated into a set of Thrift type\ndefinitions by the ",(0,a.mdx)("inlineCode",{parentName:"p"},"gen-schema")," tool (see ",(0,a.mdx)("a",{parentName:"p",href:"/docs/schema/workflow"},"Workflow"),").\nThese Thrift definitions can be used to work with Glean data in your\nclient, as native data types in whatever language you're using, either\nfor querying data or for writing facts."),(0,a.mdx)("p",null,"The Thrift types also have a JSON representation, which can be read\nand written directly. When you perform queries in the\n",(0,a.mdx)("a",{parentName:"p",href:"/docs/shell"},"shell"),", the results are printed as JSON-encoded Thrift;\nwhen you ",(0,a.mdx)("a",{parentName:"p",href:"/docs/write"},"write data to Glean")," it can be in the form of\nJSON-encoded Thrift."),(0,a.mdx)(i.FbInternalOnly,{mdxType:"FbInternalOnly"},(0,a.mdx)("p",null,"Facebook internal: the Thrift types for the schema are automatically\ngenerated into\n",(0,a.mdx)("a",{parentName:"p",href:"https://phabricator.intern.facebook.com/diffusion/FBS/browse/master/fbcode/glean/schema"},"fbcode/glean/schema"),", and those files are automatically sync'd to\nwww too.")),(0,a.mdx)("p",null,"The relationship between schema types and Thrift/JSON is given by the following table:"),(0,a.mdx)("table",null,(0,a.mdx)("thead",{parentName:"table"},(0,a.mdx)("tr",{parentName:"thead"},(0,a.mdx)("th",{parentName:"tr",align:null},"Schema type"),(0,a.mdx)("th",{parentName:"tr",align:null},"Thrift type"),(0,a.mdx)("th",{parentName:"tr",align:null},"JSON"))),(0,a.mdx)("tbody",{parentName:"table"},(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"nat")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"Nat")," (",(0,a.mdx)("inlineCode",{parentName:"td"},"i64"),")"),(0,a.mdx)("td",{parentName:"tr",align:null},"123")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"byte")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"Byte")," (",(0,a.mdx)("inlineCode",{parentName:"td"},"i8"),")"),(0,a.mdx)("td",{parentName:"tr",align:null},"123")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"string")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"string")),(0,a.mdx)("td",{parentName:"tr",align:null},'"abc"')),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"bool")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"bool")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"true")," or ",(0,a.mdx)("inlineCode",{parentName:"td"},"false"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"[byte]")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"binary")),(0,a.mdx)("td",{parentName:"tr",align:null},"base-64 encoded string ",(0,a.mdx)("sup",null,"*1"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"[T]")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"list")),(0,a.mdx)("td",{parentName:"tr",align:null},"[...]")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"set T")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"list")),(0,a.mdx)("td",{parentName:"tr",align:null},"[...]")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"{"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2081 : T\u2081,"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"...,"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2099 : T\u2099"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"struct Foo {"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"1: T\u2081 f\u2081;"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"n: T\u2099 f\u2099;"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"{"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},'"f\u2081" : q\u2081,'),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},'"f\u2099" : q\u2099'),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"{"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2081 : T\u2081 "),(0,a.mdx)("code",null,"|"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"... "),(0,a.mdx)("code",null,"|"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2099 : T\u2099"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"union Foo {"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"1: T\u2081 f\u2081;"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"n: T\u2099 f\u2099;"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},'{ "f" : t }'),(0,a.mdx)("br",null),"for one of the fields ",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2081"),"..",(0,a.mdx)("inlineCode",{parentName:"td"},"f\u2099"))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"maybe T")),(0,a.mdx)("td",{parentName:"tr",align:null},"In a record field:",(0,a.mdx)("br",null)," ",(0,a.mdx)("inlineCode",{parentName:"td"},"optional T f")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"f : t"),(0,a.mdx)("br",null)," if the value is present")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"enum {"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"L\u2081"),(0,a.mdx)("code",null,"|"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("code",null,"|"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"L\u2099"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"enum Foo { "),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"L\u2081 = 1,"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"..."),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"L\u2099 = n"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}")),(0,a.mdx)("td",{parentName:"tr",align:null},"the index of the value,",(0,a.mdx)("br",null)," e.g. 12")),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"predicate P : K -> V")),(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"struct P {"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"1: Id id"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"2: optional K key"),(0,a.mdx)("br",null),"\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},"3: optional V value"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"}"),(0,a.mdx)("br",null),"note",(0,a.mdx)("sup",null,"*2")),(0,a.mdx)("td",{parentName:"tr",align:null},"refer to fact N:",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"N")," or ",(0,a.mdx)("inlineCode",{parentName:"td"},'{ "id": N }'),(0,a.mdx)("br",null),"define a fact:",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},'{ "id" : N,'),(0,a.mdx)("br",null),"\xa0","\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},'"key" : t }')," or",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},'{ "key": t }')," or",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},'{ "key": t,'),(0,a.mdx)("br",null),"\xa0","\xa0","\xa0","\xa0",(0,a.mdx)("inlineCode",{parentName:"td"},'"value" : v }'))),(0,a.mdx)("tr",{parentName:"tbody"},(0,a.mdx)("td",{parentName:"tr",align:null},(0,a.mdx)("inlineCode",{parentName:"td"},"type N = T")),(0,a.mdx)("td",{parentName:"tr",align:null},"depending on T: ",(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"struct N { .. }"),(0,a.mdx)("br",null)," ",(0,a.mdx)("inlineCode",{parentName:"td"},"union N {...}"),(0,a.mdx)("br",null)," ",(0,a.mdx)("inlineCode",{parentName:"td"},"enum N {...}"),(0,a.mdx)("br",null),(0,a.mdx)("inlineCode",{parentName:"td"},"typedef T N;")),(0,a.mdx)("td",{parentName:"tr",align:null},"same as type T")))),(0,a.mdx)("ol",null,(0,a.mdx)("li",{parentName:"ol"},(0,a.mdx)("p",{parentName:"li"},"The Thrift encoding of a binary field in JSON is a base-64-encoded string. However, not all Thrift implementations respect this. At the time of writing, the Python Thrift implementation doesn't base-64-encode binary values. For this reason we provide an option in the Glean Thrift API to disable base-64 encoding for binary if your client doesn't support it. The Glean Shell also uses this option to make it easier to work with binary.")),(0,a.mdx)("li",{parentName:"ol"},(0,a.mdx)("p",{parentName:"li"},"the ",(0,a.mdx)("inlineCode",{parentName:"p"},"key")," is optional - a nested fact may be expanded in place or represented by a reference to the fact ID only. When querying Glean data the query specifies which nested facts should be expanded in the result, and when writing data to Glean using Thrift or JSON, we can optionally specify the value of nested facts inline."))))}s.isMDXComponent=!0},80510:function(e,n,t){var r=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.getSpecInfo=void 0;const a=t(88266);n.getSpecInfo=function(e){return r(this,void 0,void 0,(function*(){return yield a.call({module:"bloks",api:"getSpecInfo",args:{styleId:e}})}))}},88266:function(e,n){var t=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.call=void 0;let r=!1,a=0;const i={};n.call=function(e){return t(this,void 0,void 0,(function*(){if("staticdocs.thefacebook.com"!==window.location.hostname&&"localhost"!==window.location.hostname)return Promise.reject(new Error("Not running on static docs"));r||(r=!0,window.addEventListener("message",(e=>{if("static-docs-bridge-response"!==e.data.event)return;const n=e.data.id;n in i||console.error(`Recieved response for id: ${n} with no matching receiver`),"response"in e.data?i[n].resolve(e.data.response):i[n].reject(new Error(e.data.error)),delete i[n]})));const n=a++,t=new Promise(((e,t)=>{i[n]={resolve:e,reject:t}})),d={event:"static-docs-bridge-call",id:n,module:e.module,api:e.api,args:e.args},o="localhost"===window.location.hostname?"*":"https://www.internalfb.com";return window.parent.postMessage(d,o),t}))}},70680:function(e,n,t){var r=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.reportFeatureUsage=n.reportContentCopied=void 0;const a=t(88266);n.reportContentCopied=function(e){return r(this,void 0,void 0,(function*(){const{textContent:n}=e;try{yield a.call({module:"feedback",api:"reportContentCopied",args:{textContent:n}})}catch(t){}}))},n.reportFeatureUsage=function(e){return r(this,void 0,void 0,(function*(){const{featureName:n,id:t}=e;console.log("used feature");try{yield a.call({module:"feedback",api:"reportFeatureUsage",args:{featureName:n,id:t}})}catch(r){}}))}},14423:function(e,n,t){var r=this&&this.__createBinding||(Object.create?function(e,n,t,r){void 0===r&&(r=t),Object.defineProperty(e,r,{enumerable:!0,get:function(){return n[t]}})}:function(e,n,t,r){void 0===r&&(r=t),e[r]=n[t]}),a=this&&this.__setModuleDefault||(Object.create?function(e,n){Object.defineProperty(e,"default",{enumerable:!0,value:n})}:function(e,n){e.default=n}),i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var n={};if(null!=e)for(var t in e)"default"!==t&&Object.prototype.hasOwnProperty.call(e,t)&&r(n,e,t);return a(n,e),n};Object.defineProperty(n,"__esModule",{value:!0}),n.OssOnly=n.FbInternalOnly=n.getEphemeralDiffNumber=n.hasEphemeralDiffNumber=n.isInternal=n.validateFbContentArgs=n.fbInternalOnly=n.fbContent=n.inpageeditor=n.feedback=n.uidocs=n.bloks=void 0,n.bloks=i(t(80510)),n.uidocs=i(t(3730)),n.feedback=i(t(70680)),n.inpageeditor=i(t(45458));const d=["internal","external"];function o(e){return m(e),u()?"internal"in e?l(e.internal):[]:"external"in e?l(e.external):[]}function l(e){return"function"==typeof e?e():e}function m(e){if("object"!=typeof e)throw new Error(`fbContent() args must be an object containing keys: ${d}. Instead got ${e}`);if(!Object.keys(e).find((e=>d.find((n=>n===e)))))throw new Error(`No valid args found in ${JSON.stringify(e)}. Accepted keys: ${d}`);const n=Object.keys(e).filter((e=>!d.find((n=>n===e))));if(n.length>0)throw new Error(`Unexpected keys ${n} found in fbContent() args. Accepted keys: ${d}`)}function u(){try{return Boolean(!1)}catch(e){return console.log("process.env.FB_INTERNAL couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),!1}}function c(){try{return null}catch(e){return console.log("process.env.PHABRICATOR_DIFF_NUMBER couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),null}}n.fbContent=o,n.fbInternalOnly=function(e){return o({internal:e})},n.validateFbContentArgs=m,n.isInternal=u,n.hasEphemeralDiffNumber=function(){return Boolean(c())},n.getEphemeralDiffNumber=c,n.FbInternalOnly=function(e){return u()?e.children:null},n.OssOnly=function(e){return u()?null:e.children}},45458:function(e,n,t){var r=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.submitDiff=void 0;const a=t(88266);n.submitDiff=function(e){return r(this,void 0,void 0,(function*(){const{file_path:n,new_content:t,project_name:r,diff_number:i}=e;try{return yield a.call({module:"inpageeditor",api:"createPhabricatorDiffApi",args:{file_path:n,new_content:t,project_name:r,diff_number:i}})}catch(d){throw new Error(`Error occurred while trying to submit diff. Stack trace: ${d}`)}}))}},3730:function(e,n,t){var r=this&&this.__awaiter||function(e,n,t,r){return new(t||(t=Promise))((function(a,i){function d(e){try{l(r.next(e))}catch(n){i(n)}}function o(e){try{l(r.throw(e))}catch(n){i(n)}}function l(e){var n;e.done?a(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(d,o)}l((r=r.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.getApi=n.docsets=void 0;const a=t(88266);n.docsets={BLOKS_CORE:"887372105406659"},n.getApi=function(e){return r(this,void 0,void 0,(function*(){const{name:n,framework:t,docset:r}=e;return yield a.call({module:"uidocs",api:"getApi",args:{name:n,framework:t,docset:r}})}))}}}]); \ No newline at end of file diff --git a/assets/js/7c10977a.62d627bb.js b/assets/js/7c10977a.62d627bb.js deleted file mode 100644 index c4b57038b..000000000 --- a/assets/js/7c10977a.62d627bb.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[98],{15680:(e,n,a)=>{a.r(n),a.d(n,{MDXContext:()=>d,MDXProvider:()=>c,mdx:()=>x,useMDXComponents:()=>p,withMDXComponents:()=>m});var t=a(96540);function i(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function l(){return l=Object.assign||function(e){for(var n=1;n=0||(i[a]=e[a]);return i}(e,n);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var d=t.createContext({}),m=function(e){return function(n){var a=p(n.components);return t.createElement(e,l({},n,{components:a}))}},p=function(e){var n=t.useContext(d),a=n;return e&&(a="function"==typeof e?e(n):s(s({},n),e)),a},c=function(e){var n=p(e.components);return t.createElement(d.Provider,{value:n},e.children)},h="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},f=t.forwardRef((function(e,n){var a=e.components,i=e.mdxType,l=e.originalType,r=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),m=p(a),c=i,h=m["".concat(r,".").concat(c)]||m[c]||u[c]||l;return a?t.createElement(h,s(s({ref:n},d),{},{components:a})):t.createElement(h,s({ref:n},d))}));function x(e,n){var a=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var l=a.length,r=new Array(l);r[0]=f;var s={};for(var o in n)hasOwnProperty.call(n,o)&&(s[o]=n[o]);s.originalType=e,s[h]="string"==typeof e?e:i,r[1]=s;for(var d=2;d{a.r(n),a.d(n,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>r,metadata:()=>o,toc:()=>m});var t=a(58168),i=(a(96540),a(15680)),l=a(14423);const r={id:"guide",title:"Angle Guide",sidebar_label:"Guide"},s=void 0,o={unversionedId:"angle/guide",id:"angle/guide",title:"Angle Guide",description:"The following guide will explain Angle from first principles, leading you through from simple queries to more complex ones.",source:"@site/docs/angle/guide.md",sourceDirName:"angle",slug:"/angle/guide",permalink:"/docs/angle/guide",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/angle/guide.md",tags:[],version:"current",frontMatter:{id:"guide",title:"Angle Guide",sidebar_label:"Guide"},sidebar:"someSidebar",previous:{title:"Introduction",permalink:"/docs/angle/intro"},next:{title:"Query Efficiency",permalink:"/docs/angle/efficiency"}},d={},m=[{value:"Just the facts",id:"just-the-facts",level:2},{value:"Matching nested facts",id:"matching-nested-facts",level:2},{value:"Union types",id:"union-types",level:2},{value:"Maybe",id:"maybe",level:2},{value:"Choice",id:"choice",level:2},{value:"Variables and more complex queries",id:"variables-and-more-complex-queries",level:2},{value:"Statements",id:"statements",level:2},{value:"Dot syntax",id:"dot-syntax",level:2},{value:"Dot syntax for union types",id:"dot-syntax-for-union-types",level:3},{value:"Extracting the key of a fact",id:"extracting-the-key-of-a-fact",level:3},{value:"If-then-else",id:"if-then-else",level:2},{value:"Arrays",id:"arrays",level:2},{value:"String prefix",id:"string-prefix",level:2},{value:"Tuples",id:"tuples",level:2},{value:"Enums and bool",id:"enums-and-bool",level:2},{value:"Negation",id:"negation",level:2}],p={toc:m},c="wrapper";function h(e){let{components:n,...a}=e;return(0,i.mdx)(c,(0,t.A)({},p,a,{components:n,mdxType:"MDXLayout"}),(0,i.mdx)("p",null,"The following guide will explain Angle from first principles, leading you through from simple queries to more complex ones."),(0,i.mdx)("p",null,"If you want to try the examples for yourself, or experiment with\nchanges to the example schema, you should first follow the\ninstructions in ",(0,i.mdx)("a",{parentName:"p",href:"/docs/walkthrough"},"Walkthrough")," to get set up."),(0,i.mdx)(l.FbInternalOnly,{mdxType:"FbInternalOnly"},(0,i.mdx)("p",null,"There are also ",(0,i.mdx)("a",{parentName:"p",href:"https://www.internalfb.com/intern/wiki/Glean/Query/Angle/Angle_examples/"},"examples of using Angle")," to query real data.")),(0,i.mdx)("h2",{id:"just-the-facts"},"Just the facts"),(0,i.mdx)("p",null,"Data in Glean is described by a ",(0,i.mdx)("em",{parentName:"p"},"schema"),", which we normally put in a file with the extension ",(0,i.mdx)("inlineCode",{parentName:"p"},"angle"),". For the purposes of this guide we\u2019ll use the example schema in ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.angle"),". Full details about defining schemas can be found in ",(0,i.mdx)("a",{parentName:"p",href:"/docs/schema/basic"},"Schemas"),". The ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.angle")," file contains a schema definition like this:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"schema example.1 {\n\n# definitions go here\n\n}\n")),(0,i.mdx)("p",null,"This says we\u2019re defining a schema called ",(0,i.mdx)("inlineCode",{parentName:"p"},"example"),", with version 1."),(0,i.mdx)("p",null,"The schema contains definitions for ",(0,i.mdx)("em",{parentName:"p"},"predicates"),". A predicate is the type of ",(0,i.mdx)("em",{parentName:"p"},"facts"),", which are the individual pieces of information that Glean stores. Our example schema models a simplified class hierarchy for an object-oriented language, starting with a predicate for a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Class :\n {\n name : string,\n line : nat,\n }\n")),(0,i.mdx)("p",null,"This says that the facts of ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class")," are records with two fields, a ",(0,i.mdx)("inlineCode",{parentName:"p"},"name")," field which contains a ",(0,i.mdx)("inlineCode",{parentName:"p"},"string"),", and a ",(0,i.mdx)("inlineCode",{parentName:"p"},"line")," field which contains a ",(0,i.mdx)("inlineCode",{parentName:"p"},"nat")," (\u201cnat\u201d is short for \u201cnatural number\u201d, which is limited to 64 bits in Glean)."),(0,i.mdx)("p",null,"The simplest type of Angle query is one that just selects facts from\nthe database that match a pattern. For our first Angle query, let\u2019s\nfind a class by its name:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'facts> example.Class { name = "Pet" }\n{ "id": 1024, "key": { "name": "Pet", "line": 10 } }\n\n1 results, 1 facts, 4.61ms, 117632 bytes, 677 compiled bytes\n')),(0,i.mdx)("p",null,"(The last line contains statistics about query performance from Glean; I\u2019ll leave this out in the rest of the examples.)"),(0,i.mdx)("p",null,"What\u2019s going on here?"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"The query consists of the ",(0,i.mdx)("em",{parentName:"li"},"predicate name")," ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Class")," followed by a ",(0,i.mdx)("em",{parentName:"li"},"pattern")," ",(0,i.mdx)("inlineCode",{parentName:"li"},'{ name = "Pet" }')),(0,i.mdx)("li",{parentName:"ul"},"Note that when we refer to a predicate in a query, the name is ",(0,i.mdx)("em",{parentName:"li"},"qualified")," by prefixing the schema name, so it\u2019s ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Class")," rather than just ",(0,i.mdx)("inlineCode",{parentName:"li"},"Class"),"."),(0,i.mdx)("li",{parentName:"ul"},"The query returns all the facts of ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Class")," that match the pattern")),(0,i.mdx)("p",null,"The shell shows results in JSON format. When you\u2019re making Glean\nqueries from code, the results will normally be decoded into native\ndata types that you can manipulate directly in whatever language\nyou\u2019re using; for more details see ",(0,i.mdx)("a",{parentName:"p",href:"/docs/schema/thrift"},"Thrift and\nJSON"),"."),(0,i.mdx)("p",null,"Note that each fact has a unique ",(0,i.mdx)("inlineCode",{parentName:"p"},"id"),". This is how Glean identifies facts in its database. As a user you normally won\u2019t have to worry about fact ",(0,i.mdx)("inlineCode",{parentName:"p"},"id"),"s; you can think of them like memory addresses."),(0,i.mdx)("p",null,"The pattern specifies which facts to return. In the example above, our pattern is matching a record type and specifying a subset of the fields: just the ",(0,i.mdx)("inlineCode",{parentName:"p"},"name")," field. We could match the ",(0,i.mdx)("inlineCode",{parentName:"p"},"line")," field instead:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=sh"},'facts> example.Class { line = 20 }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n')),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"Your patterns should normally match fields at the ",(0,i.mdx)("em",{parentName:"p"},"beginning")," of the\nrecord, because facts in the database are indexed by a prefix of the\nfields. Matching a field in the middle of the record works by scanning\nall the facts, which could be expensive. We\u2019ll get into this in more\ndetail in ",(0,i.mdx)("a",{parentName:"p",href:"/docs/angle/efficiency"},"Query Efficiency"),".")),(0,i.mdx)("p",null,'What other kinds of patterns can we use? Well, the simplest patterns are the wildcard, \u201c_\u201d, which matches anything, and "never", which always fails to match.'),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Class _\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n{ "id": 1027, "key": { "name": "Goldfish", "line": 40 } }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n{ "id": 1024, "key": { "name": "Pet", "line": 10 } }\nfacts> example.Class never\n(no results)\n')),(0,i.mdx)("p",null,"We\u2019ll introduce more kinds of pattern in the following sections. The full list of patterns can be found in ",(0,i.mdx)("a",{parentName:"p",href:"/docs/angle/reference"},"Angle Reference"),"."),(0,i.mdx)("h2",{id:"matching-nested-facts"},"Matching nested facts"),(0,i.mdx)("p",null,"The real power of Glean comes from relationships between facts. Facts can refer directly to other facts, and we can write queries that directly match on these connections."),(0,i.mdx)("p",null,"Our example schema has a predicate that expresses the inheritance relationship between classes:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Parent :\n {\n child : Class,\n parent : Class,\n }\n")),(0,i.mdx)("p",null,"Let\u2019s find what ",(0,i.mdx)("inlineCode",{parentName:"p"},"Fish")," inherits from:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Parent { child = { name = "Fish" }}\n{\n "id": 1029,\n "key": { "child": { "id": 1026, "key": { "name": "Fish", "line": 30 } }, "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } } }\n}\n')),(0,i.mdx)("p",null,"Let\u2019s break this down."),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},(0,i.mdx)("inlineCode",{parentName:"li"},'{ child = { name = "Fish" }}')," is a pattern that matches the key type of ",(0,i.mdx)("inlineCode",{parentName:"li"},"Parent")),(0,i.mdx)("li",{parentName:"ul"},"So, looking at the schema, ",(0,i.mdx)("inlineCode",{parentName:"li"},'{ name = "Fish" }')," is a pattern that should match the ",(0,i.mdx)("inlineCode",{parentName:"li"},"Class")," in the field ",(0,i.mdx)("inlineCode",{parentName:"li"},"child"),".")),(0,i.mdx)("p",null,"By default Angle queries recursively expand facts in the results. We can see in the above result that the ",(0,i.mdx)("inlineCode",{parentName:"p"},"child")," and ",(0,i.mdx)("inlineCode",{parentName:"p"},"parent")," fields contain the full facts they point to. If we want the result to be \u201cshallow\u201d, meaning it contains just the facts that match and not the nested facts, we can ask Glean to not expand the content of those references. In the shell this is done by running the command ",(0,i.mdx)("inlineCode",{parentName:"p"},":expand off"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> :expand off\nfacts> example.Parent { child = { name = "Fish" }}\n{ "id": 1029, "key": { "child": { "id": 1026 }, "parent": { "id": 1024 } } }\n')),(0,i.mdx)("p",null,"We can of course go the other way and find all the children of a class:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Parent { parent = { name = "Pet" }}\n{\n "id": 1028,\n "key": {\n "child": { "id": 1025, "key": { "name": "Lizard", "line": 20 } },\n "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } }\n }\n}\n{\n "id": 1029,\n "key": {\n "child": { "id": 1026, "key": { "name": "Fish", "line": 30 } },\n "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } }\n }\n}\n')),(0,i.mdx)("p",null,"But as before, note that this would be an inefficient query if we had a lot of data because the pattern is matching on the second field of ",(0,i.mdx)("inlineCode",{parentName:"p"},"Parent")," (namely ",(0,i.mdx)("inlineCode",{parentName:"p"},"parent"),"). Later we\u2019ll see how to make these queries more efficient using a derived predicate."),(0,i.mdx)("h2",{id:"union-types"},"Union types"),(0,i.mdx)("p",null,"Our examples so far have dealt with record types. Glean also supports ",(0,i.mdx)("em",{parentName:"p"},"union types"),", also called ",(0,i.mdx)("em",{parentName:"p"},"sum types"),", which are used to express multiple alternatives. For example, let\u2019s expand our schema to include class members which can be either a method or a variable:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Has :\n {\n class_ : Class,\n has : Member,\n access : enum { public_ | private_ },\n }\n\npredicate Member :\n {\n method : { name : string, doc : maybe string } |\n variable : { name : string }\n }\n")),(0,i.mdx)("p",null,"The predicate ",(0,i.mdx)("inlineCode",{parentName:"p"},"Has")," maps a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class")," to a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Member")," (with a ",(0,i.mdx)("inlineCode",{parentName:"p"},"public_")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"private_")," annotation), and a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Member")," is either ",(0,i.mdx)("inlineCode",{parentName:"p"},"method")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"variable"),", with some associated data. Note that a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class")," might have more than one ",(0,i.mdx)("inlineCode",{parentName:"p"},"Member"),", which is fine: there can be multiple ",(0,i.mdx)("inlineCode",{parentName:"p"},"Has")," facts for a given ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class"),"."),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"The schema uses ",(0,i.mdx)("inlineCode",{parentName:"p"},"class_")," rather than ",(0,i.mdx)("inlineCode",{parentName:"p"},"class")," as a field name, because\n",(0,i.mdx)("inlineCode",{parentName:"p"},"class")," is a reserved word in Angle. Similarly, we added trailing\nunderscores to ",(0,i.mdx)("inlineCode",{parentName:"p"},"public_")," and",(0,i.mdx)("inlineCode",{parentName:"p"},"private_")," for the same reason."),(0,i.mdx)("p",{parentName:"admonition"},"There are many such reserved words, which are reserved not because Angle uses them, but because they cause problems for code that is automatically generated from the schema. To avoid having too many ad-hoc language-specific naming rules, Glean prevents certain problematic names from being used in the schema. The Angle compiler will tell you if you try to use a reserved word.")),(0,i.mdx)("p",null,"Let\u2019s find classes that have a variable called ",(0,i.mdx)("inlineCode",{parentName:"p"},"fins"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Has { has = { variable = { name = "fins" }}}\n{\n "id": 1036,\n "key": {\n "class_": { "id": 1026, "key": { "name": "Fish", "line": 30 } },\n "has": { "id": 1035, "key": { "variable": { "name": "fins" } } },\n "access": 1\n }\n}\n')),(0,i.mdx)("p",null,"The key thing here is that we matched on ",(0,i.mdx)("inlineCode",{parentName:"p"},"Member")," which is a union type, using the pattern ",(0,i.mdx)("inlineCode",{parentName:"p"},'{ variable = { name = "fins" }}'),". A pattern to match a union type looks very much like a record pattern, but it can have only a single field, in this case either ",(0,i.mdx)("inlineCode",{parentName:"p"},"variable")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"method"),"."),(0,i.mdx)("h2",{id:"maybe"},"Maybe"),(0,i.mdx)("p",null,"Glean has one built-in union type called ",(0,i.mdx)("inlineCode",{parentName:"p"},"maybe"),", which is useful when we want to have optional values in the data. It's used in our example schema to attach optional documentation to a class member:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Member :\n {\n method : { name : string, doc : maybe string } |\n variable : { name : string }\n }\n")),(0,i.mdx)("p",null,"The type ",(0,i.mdx)("inlineCode",{parentName:"p"},"maybe string")," behaves exactly as if it were defined as the union type ",(0,i.mdx)("inlineCode",{parentName:"p"},"{ nothing | just : string }"),". That means we can write a pattern that matches it, exactly as we would write a pattern for ",(0,i.mdx)("inlineCode",{parentName:"p"},"{ nothing | just : string }"),":"),(0,i.mdx)("p",null,"Methods without documentation:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"facts> example.Member { method = { doc = nothing } }\n")),(0,i.mdx)("p",null,"Methods with documentation:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"facts> example.Member { method = { doc = { just = _ }}}\n")),(0,i.mdx)("h2",{id:"choice"},"Choice"),(0,i.mdx)("p",null,"In a pattern we can express multiple alternatives by separating patterns with a vertical bar ",(0,i.mdx)("inlineCode",{parentName:"p"},"|"),"."),(0,i.mdx)("p",null,"For example, we can find classes on lines 20 or 30:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Class { line = 20 | 30 }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n')),(0,i.mdx)("p",null,"Or we can find all the classes that have either a ",(0,i.mdx)("inlineCode",{parentName:"p"},"method")," called ",(0,i.mdx)("inlineCode",{parentName:"p"},"feed")," or a ",(0,i.mdx)("inlineCode",{parentName:"p"},"variable")," with any name:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Has { has = { method = { name = "feed" }} | { variable = _ }}\n\n(results omitted)\n')),(0,i.mdx)("h2",{id:"variables-and-more-complex-queries"},"Variables and more complex queries"),(0,i.mdx)("p",null,"So far we\u2019ve seen how to query for facts by matching patterns, including matching nested facts. In this section we\u2019ll see how to construct more complex queries that combine matching facts from multiple predicates."),(0,i.mdx)("p",null,"Suppose we want to find all the parents of classes that have a variable called ",(0,i.mdx)("inlineCode",{parentName:"p"},"fins"),". We need to build a query that will"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"find the classes with a variable called ",(0,i.mdx)("inlineCode",{parentName:"li"},"fins")," using ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Has")," as we did above"),(0,i.mdx)("li",{parentName:"ul"},"find their parents using ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Parent"))),(0,i.mdx)("p",null,"We can combine these two as follows:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'example.Has\n {\n class_ = C,\n has = { variable = { name = "fins" }}\n };\nexample.Parent { child = C }\n')),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"I\u2019ve written this on several lines with indentation to illustrate it\nbetter, to do this in the shell you will need to use the ",(0,i.mdx)("inlineCode",{parentName:"p"},":edit"),"\ncommand to put the query in a temporary file.")),(0,i.mdx)("p",null,"The key thing here is that we used a ",(0,i.mdx)("em",{parentName:"p"},"variable")," ",(0,i.mdx)("inlineCode",{parentName:"p"},"C")," to stand for the ",(0,i.mdx)("inlineCode",{parentName:"p"},"class_")," field when matching facts of ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.Has"),", and then we searched for ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.Parent")," facts with the same value of ",(0,i.mdx)("inlineCode",{parentName:"p"},"C")," for the ",(0,i.mdx)("inlineCode",{parentName:"p"},"child")," field."),(0,i.mdx)("p",null,"Note that variables must ",(0,i.mdx)("em",{parentName:"p"},"always")," begin with an upper-case letter, while schema names (",(0,i.mdx)("inlineCode",{parentName:"p"},"example)")," and field names (",(0,i.mdx)("inlineCode",{parentName:"p"},"child"),") begin with a lower-case letter."),(0,i.mdx)("p",null,"The semicolon separates multiple ",(0,i.mdx)("em",{parentName:"p"},"statements")," in a query. When there are multiple statements the results of the query are the facts that match the last statement, in this case the ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.Parent"),". Let\u2019s try it:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Has { class_ = C, has = { variable = { name = "fins" }}}; example.Parent { child = C }\n{\n "id": 1029,\n "key": {\n "child": { "id": 1026, "key": { "name": "Fish", "line": 30 } },\n "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } }\n }\n}\n')),(0,i.mdx)("p",null," Suppose we don\u2019t care too much about the child here, we only care about getting a list of the parents. We can avoid returning the redundant information by specifying explicitly what it is we want to return from the query:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'P where\n example.Has\n {\n class_ = C,\n has = { variable = { name = "fins" }}\n };\n example.Parent { child = C, parent = P }\n')),(0,i.mdx)("p",null,"The general form of the query is ",(0,i.mdx)("em",{parentName:"p"},(0,i.mdx)("inlineCode",{parentName:"em"},"expression"))," ",(0,i.mdx)("inlineCode",{parentName:"p"},"where")," ",(0,i.mdx)("em",{parentName:"p"},(0,i.mdx)("inlineCode",{parentName:"em"},"statements")),", where ",(0,i.mdx)("em",{parentName:"p"},(0,i.mdx)("inlineCode",{parentName:"em"},"expression"))," is an arbitrary expression and each statement is a pattern that matches some facts. The results of the query are the distinct values of ",(0,i.mdx)("em",{parentName:"p"},(0,i.mdx)("inlineCode",{parentName:"em"},"expression"))," for which all the statements match facts in the database."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> P where example.Has { class_ = C, has = { variable = { name = "fins" }}}; example.Parent { child = C, parent = P }\n{ "id": 1024, "key": { "name": "Pet", "line": 10 } }\n')),(0,i.mdx)("h2",{id:"statements"},"Statements"),(0,i.mdx)("p",null,"In general, a statement can be of the form ",(0,i.mdx)("em",{parentName:"p"},"A = B.")," For example, if we write"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'C = example.Class { name = "Fish" };\nexample.Parent { child = C }\n')),(0,i.mdx)("p",null,"that\u2019s the same as"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'example.Parent { child = { name = "Fish" }}\n')),(0,i.mdx)("p",null,"A statement can have a pattern on either side, for example"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'C where\n C = example.Class { name = N };\n N = "Fish" | "Goldfish"\n')),(0,i.mdx)("p",null,"A statement can itself be a set of alternatives separated by a vertical bar ",(0,i.mdx)("inlineCode",{parentName:"p"},"|"),". For example, we can find classes that are either a parent of the ",(0,i.mdx)("inlineCode",{parentName:"p"},"Goldfish")," or have a ",(0,i.mdx)("inlineCode",{parentName:"p"},"feed")," method:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'C where\n example.Parent { child = { name = "Goldfish" }, parent = C } |\n example.Has { class_ = C, has = { method = { name = "feed" }}}\n')),(0,i.mdx)("h2",{id:"dot-syntax"},"Dot syntax"),(0,i.mdx)("p",null,'So far we\'ve been extracting fields from records by writing patterns,\nbut we can also extract fields from records using the traditional "dot\nsyntax". For example, instead of'),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'example.Parent { child = { name = "Fish" }}\n')),(0,i.mdx)("p",null,"we could write"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'example.Parent P where P.child.name = "Fish"\n')),(0,i.mdx)("p",null,"Here"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},(0,i.mdx)("inlineCode",{parentName:"li"},"example.Parent P")," selects facts of ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Parent")," and binds the key to the variable ",(0,i.mdx)("inlineCode",{parentName:"li"},"P")),(0,i.mdx)("li",{parentName:"ul"},(0,i.mdx)("inlineCode",{parentName:"li"},'P.child.name = "Fish"')," is a constraint on the ",(0,i.mdx)("inlineCode",{parentName:"li"},"name")," field of the ",(0,i.mdx)("inlineCode",{parentName:"li"},"child")," field of ",(0,i.mdx)("inlineCode",{parentName:"li"},"P")),(0,i.mdx)("li",{parentName:"ul"},"the query returns all facts of ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Parent")," that satisfy the constraint")),(0,i.mdx)("p",null,"Dot syntax tends to be more concise than patterns when there are\ndeeply-nested records, because it avoids all the nested braces."),(0,i.mdx)("h3",{id:"dot-syntax-for-union-types"},"Dot syntax for union types"),(0,i.mdx)("p",null,"Matching union types can also be achieved using dot syntax. For\nexample, earlier we had"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'example.Has { has = { variable = { name = "fins" }}}\n')),(0,i.mdx)("p",null,"using dot syntax this would be"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'example.Has H where H.has.variable?.name = "fins"\n')),(0,i.mdx)("p",null,"Note that when selecting a union type we add a '?' suffix,\nas with ",(0,i.mdx)("inlineCode",{parentName:"p"},".variable?")," in the example above. This makes it more obvious\nthat we're doing something conditional: if ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.has")," is not a\n",(0,i.mdx)("inlineCode",{parentName:"p"},"variable"),", then ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.has.variable?")," has no values."),(0,i.mdx)("p",null,"Selecting from union types works nicely with choice (",(0,i.mdx)("inlineCode",{parentName:"p"},"|"),"):"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"Name where\n example.Has H;\n Name = (H.has.variable?.name | H.has.method?.name)\n")),(0,i.mdx)("p",null,"returns all the names of variables and methods."),(0,i.mdx)("h3",{id:"extracting-the-key-of-a-fact"},"Extracting the key of a fact"),(0,i.mdx)("p",null,"It's sometimes useful to be able to extract the key of a fact. If we\nhave a variable ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," of some predicate type, then we can extract the\nkey of ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," with ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.*"),". If ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," has type ",(0,i.mdx)("inlineCode",{parentName:"p"},"P")," and ",(0,i.mdx)("inlineCode",{parentName:"p"},"P")," is a predicate with\nkey type ",(0,i.mdx)("inlineCode",{parentName:"p"},"K"),", then ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.*")," has type ",(0,i.mdx)("inlineCode",{parentName:"p"},"K"),"."),(0,i.mdx)("p",null,"Usually we don't need to extract the key explicitly because Angle does\nit automatically. For example if ",(0,i.mdx)("inlineCode",{parentName:"p"},"X : example.Class"),", then ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.name")," is\nshorthand for ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.*.name"),". But sometimes we just want the key without\nselecting a field, or perhaps the key isn't a record. In those cases,\n",(0,i.mdx)("inlineCode",{parentName:"p"},"X.*")," can be useful."),(0,i.mdx)("h2",{id:"if-then-else"},"If-then-else"),(0,i.mdx)("p",null,"We can conditionally match patterns using ",(0,i.mdx)("inlineCode",{parentName:"p"},"if then else"),"."),(0,i.mdx)("p",null,"Variables matched in the condition will be available in the ",(0,i.mdx)("inlineCode",{parentName:"p"},"then")," branch."),(0,i.mdx)("p",null,"Whilst a choice will always evaluate both of its branches, the ",(0,i.mdx)("inlineCode",{parentName:"p"},"else")," branch of an if will\nnever be evaluated if the condition succeeds at least once."),(0,i.mdx)("p",null,"For example, we could get all child classes if inheritance is being used in the codebase, or\nretrieve all classes if it isn't."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'facts > if (example.Parent { child = X }) then X else example.Class _\n { "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n { "id": 1026, "key": { "name": "Fish", "line": 30 } }\n { "id": 1027, "key": { "name": "Goldfish", "line": 40 } }\n')),(0,i.mdx)("p",null,"Please note that ",(0,i.mdx)("inlineCode",{parentName:"p"},"if")," cannot be used in stored derived predicates. This\nis the case because they require the use of negation, which is disallowed in\nstored predicates."),(0,i.mdx)("h2",{id:"arrays"},"Arrays"),(0,i.mdx)("p",null,"When the schema uses an array, we need to be able to write queries that traverse the elements of the array. For example, a common use of an array is to represent the list of declarations in a source file. Our example schema defines the ",(0,i.mdx)("inlineCode",{parentName:"p"},"FileClasses")," predicate:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate FileClasses :\n {\n file : string,\n classes : [Class]\n }\n")),(0,i.mdx)("p",null,"The goal here is to map efficiently from a filename to the list of classes defined in that file. Suppose we want to write a query that finds all the classes called ",(0,i.mdx)("inlineCode",{parentName:"p"},"Goldfish")," in the file ",(0,i.mdx)("inlineCode",{parentName:"p"},"petshop.example"),", we could do it like this:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'example.FileClasses { file = "petshop.example", classes = Cs };\n{ name = "Goldfish" } = Cs[..]\n')),(0,i.mdx)("p",null,"The second line is the interesting one: ",(0,i.mdx)("inlineCode",{parentName:"p"},'{ name = "Goldfish" } = Cs[..]')," means"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"on the right-hand side, ",(0,i.mdx)("inlineCode",{parentName:"li"},"Cs[..]")," means \u201ceach element of the array ",(0,i.mdx)("inlineCode",{parentName:"li"},"Cs"),"\u201d"),(0,i.mdx)("li",{parentName:"ul"},"the left-hand side is a pattern, filtering only those ",(0,i.mdx)("inlineCode",{parentName:"li"},"Class")," facts that match ",(0,i.mdx)("inlineCode",{parentName:"li"},'{ name = "Goldfish" }'))),(0,i.mdx)("p",null,"We can also match the whole array with a pattern of the form ",(0,i.mdx)("inlineCode",{parentName:"p"},"[ p1, p2, .. ]")),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> X where [_,X,_] = [1,2,3]\n{ "id": 1040, "key": 2 }\n')),(0,i.mdx)("p",null,"Or if we don't care about the length of the array:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> X where [_,X, ..] = [1,2,3]\n{ "id": 1040, "key": 2 }\n')),(0,i.mdx)("h2",{id:"string-prefix"},"String prefix"),(0,i.mdx)("p",null,"We\u2019ve seen many examples of patterns that match strings. Glean also supports matching strings by ",(0,i.mdx)("em",{parentName:"p"},"prefix"),"; for example:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Class { name = "F".. }\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n')),(0,i.mdx)("p",null,"The syntax ",(0,i.mdx)("inlineCode",{parentName:"p"},'"F"..')," means ",(0,i.mdx)("em",{parentName:"p"},"strings beginning with the prefix")," ",(0,i.mdx)("inlineCode",{parentName:"p"},'\u201dF"'),"."),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"Why only prefix and not substring matching in general? Prefix matching can be supported efficiently by Glean\u2019s prefix-tree representation of the fact database. Other kinds of string matching could be supported, but they wouldn\u2019t be able to exploit the database representation so there\u2019s little advantage to implementing them in Angle compared with filtering on the client-side.")),(0,i.mdx)("h2",{id:"tuples"},"Tuples"),(0,i.mdx)("p",null,"A ",(0,i.mdx)("em",{parentName:"p"},"tuple")," is just a a way of writing a record without the field names. So for example, instead of"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"example.Parent { child = C }\n")),(0,i.mdx)("p",null,"we could write"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"example.Parent { C, _ }\n")),(0,i.mdx)("p",null,"When using a tuple you have to list ",(0,i.mdx)("em",{parentName:"p"},"all")," the fields, in the same order as they are declared in the schema. That's why ",(0,i.mdx)("inlineCode",{parentName:"p"},"{ child = C }")," becomes ",(0,i.mdx)("inlineCode",{parentName:"p"},"{ C, _ }")," when written as a tuple."),(0,i.mdx)("p",null,"There are upsides and downsides to using the tuple notation:"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"Pro: more concise"),(0,i.mdx)("li",{parentName:"ul"},"Con: brittle and sensitive to changes in the schema. If we add a field, then tuple patterns will break whereas record patterns won't.")),(0,i.mdx)("p",null,'As a rule of thumb we tend to use tuple syntax in cases where the predicate is "obviously" a relation, such as ',(0,i.mdx)("inlineCode",{parentName:"p"},"example.Parent"),", but we wouldn't use tuple syntax for more complex records."),(0,i.mdx)("h2",{id:"enums-and-bool"},"Enums and bool"),(0,i.mdx)("p",null,"An ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," type is a set of named constants. In the ",(0,i.mdx)("inlineCode",{parentName:"p"},"Has")," predicate we used an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," type to indicate whether a class member is ",(0,i.mdx)("inlineCode",{parentName:"p"},"public_")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"private_"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Has :\n {\n class_ : Class,\n has : Member,\n access : enum { public_ | private_ },\n }\n")),(0,i.mdx)("p",null,"To match an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," we just use the appropriate identifier, in this case ",(0,i.mdx)("inlineCode",{parentName:"p"},"public_")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"private"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Has { access = private_ }\n{ "id": 1036, "key": { "class_": { "id": 1026 }, "has": { "id": 1035 }, "access": 1 } }\n')),(0,i.mdx)("p",null,"Note that in the JSON format results, an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," is represented by an integer. When you make queries in code, the ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," will be represented by an appropriate type, such as a ",(0,i.mdx)("inlineCode",{parentName:"p"},"data")," type in Haskell."),(0,i.mdx)("p",null,"The boolean type ",(0,i.mdx)("inlineCode",{parentName:"p"},"bool")," is a special case of an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum"),", defined like this:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"type bool = enum { false | true }\n")),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"Normally the constants of an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," should begin with a lower case\nletter. You can use constants beginning with an upper-case letter, but\nto distinguish the constant from a variable name you may sometimes\nneed to provide a type signature, e.g. ",(0,i.mdx)("inlineCode",{parentName:"p"},"Constant : Type")," rather than\njust ",(0,i.mdx)("inlineCode",{parentName:"p"},"Constant"),".")),(0,i.mdx)("h2",{id:"negation"},"Negation"),(0,i.mdx)("p",null,"If we want results that do not match a certain criterion, we can use ",(0,i.mdx)("inlineCode",{parentName:"p"},"!")," to\nspecify a subquery that should fail. A subquery fails if it doesn't return any\nresult."),(0,i.mdx)("p",null,"For example, we can find classes that don't have methods"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> C where C = example.Class _; !(example.Has { class_ = C, has = { method = _ } })\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n{ "id": 1027, "key": { "name": "Goldfish", "line": 40 } }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n')),(0,i.mdx)("p",null,"Or we could find the maximum element in an array"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> X where Values = [5,1,2,3]; X = Values[..]; !(Y = Values[..]; Y > X)\n{ "id": 1091, "key": 5 }\n')),(0,i.mdx)("p",null,"The query asks for the ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," for which given all values of ",(0,i.mdx)("inlineCode",{parentName:"p"},"Y")," ",(0,i.mdx)("em",{parentName:"p"},"none")," is greater\nthan it. If ",(0,i.mdx)("inlineCode",{parentName:"p"},"Y = Values[..]")," were outside of the negation, the meaning would\nbe give me all ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," for which there is ",(0,i.mdx)("em",{parentName:"p"},"at least one")," ",(0,i.mdx)("inlineCode",{parentName:"p"},"Y")," that is not greater\nthan it. The answer to that would be all elements."))}h.isMDXComponent=!0},80510:function(e,n,a){var t=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.getSpecInfo=void 0;const i=a(88266);n.getSpecInfo=function(e){return t(this,void 0,void 0,(function*(){return yield i.call({module:"bloks",api:"getSpecInfo",args:{styleId:e}})}))}},88266:function(e,n){var a=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.call=void 0;let t=!1,i=0;const l={};n.call=function(e){return a(this,void 0,void 0,(function*(){if("staticdocs.thefacebook.com"!==window.location.hostname&&"localhost"!==window.location.hostname)return Promise.reject(new Error("Not running on static docs"));t||(t=!0,window.addEventListener("message",(e=>{if("static-docs-bridge-response"!==e.data.event)return;const n=e.data.id;n in l||console.error(`Recieved response for id: ${n} with no matching receiver`),"response"in e.data?l[n].resolve(e.data.response):l[n].reject(new Error(e.data.error)),delete l[n]})));const n=i++,a=new Promise(((e,a)=>{l[n]={resolve:e,reject:a}})),r={event:"static-docs-bridge-call",id:n,module:e.module,api:e.api,args:e.args},s="localhost"===window.location.hostname?"*":"https://www.internalfb.com";return window.parent.postMessage(r,s),a}))}},70680:function(e,n,a){var t=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.reportFeatureUsage=n.reportContentCopied=void 0;const i=a(88266);n.reportContentCopied=function(e){return t(this,void 0,void 0,(function*(){const{textContent:n}=e;try{yield i.call({module:"feedback",api:"reportContentCopied",args:{textContent:n}})}catch(a){}}))},n.reportFeatureUsage=function(e){return t(this,void 0,void 0,(function*(){const{featureName:n,id:a}=e;console.log("used feature");try{yield i.call({module:"feedback",api:"reportFeatureUsage",args:{featureName:n,id:a}})}catch(t){}}))}},14423:function(e,n,a){var t=this&&this.__createBinding||(Object.create?function(e,n,a,t){void 0===t&&(t=a),Object.defineProperty(e,t,{enumerable:!0,get:function(){return n[a]}})}:function(e,n,a,t){void 0===t&&(t=a),e[t]=n[a]}),i=this&&this.__setModuleDefault||(Object.create?function(e,n){Object.defineProperty(e,"default",{enumerable:!0,value:n})}:function(e,n){e.default=n}),l=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var n={};if(null!=e)for(var a in e)"default"!==a&&Object.prototype.hasOwnProperty.call(e,a)&&t(n,e,a);return i(n,e),n};Object.defineProperty(n,"__esModule",{value:!0}),n.OssOnly=n.FbInternalOnly=n.getEphemeralDiffNumber=n.hasEphemeralDiffNumber=n.isInternal=n.validateFbContentArgs=n.fbInternalOnly=n.fbContent=n.inpageeditor=n.feedback=n.uidocs=n.bloks=void 0,n.bloks=l(a(80510)),n.uidocs=l(a(3730)),n.feedback=l(a(70680)),n.inpageeditor=l(a(45458));const r=["internal","external"];function s(e){return d(e),m()?"internal"in e?o(e.internal):[]:"external"in e?o(e.external):[]}function o(e){return"function"==typeof e?e():e}function d(e){if("object"!=typeof e)throw new Error(`fbContent() args must be an object containing keys: ${r}. Instead got ${e}`);if(!Object.keys(e).find((e=>r.find((n=>n===e)))))throw new Error(`No valid args found in ${JSON.stringify(e)}. Accepted keys: ${r}`);const n=Object.keys(e).filter((e=>!r.find((n=>n===e))));if(n.length>0)throw new Error(`Unexpected keys ${n} found in fbContent() args. Accepted keys: ${r}`)}function m(){try{return Boolean(!1)}catch(e){return console.log("process.env.FB_INTERNAL couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),!1}}function p(){try{return null}catch(e){return console.log("process.env.PHABRICATOR_DIFF_NUMBER couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),null}}n.fbContent=s,n.fbInternalOnly=function(e){return s({internal:e})},n.validateFbContentArgs=d,n.isInternal=m,n.hasEphemeralDiffNumber=function(){return Boolean(p())},n.getEphemeralDiffNumber=p,n.FbInternalOnly=function(e){return m()?e.children:null},n.OssOnly=function(e){return m()?null:e.children}},45458:function(e,n,a){var t=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.submitDiff=void 0;const i=a(88266);n.submitDiff=function(e){return t(this,void 0,void 0,(function*(){const{file_path:n,new_content:a,project_name:t,diff_number:l}=e;try{return yield i.call({module:"inpageeditor",api:"createPhabricatorDiffApi",args:{file_path:n,new_content:a,project_name:t,diff_number:l}})}catch(r){throw new Error(`Error occurred while trying to submit diff. Stack trace: ${r}`)}}))}},3730:function(e,n,a){var t=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.getApi=n.docsets=void 0;const i=a(88266);n.docsets={BLOKS_CORE:"887372105406659"},n.getApi=function(e){return t(this,void 0,void 0,(function*(){const{name:n,framework:a,docset:t}=e;return yield i.call({module:"uidocs",api:"getApi",args:{name:n,framework:a,docset:t}})}))}}}]); \ No newline at end of file diff --git a/assets/js/7c10977a.901c28ec.js b/assets/js/7c10977a.901c28ec.js new file mode 100644 index 000000000..4548af164 --- /dev/null +++ b/assets/js/7c10977a.901c28ec.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[98],{15680:(e,n,a)=>{a.r(n),a.d(n,{MDXContext:()=>d,MDXProvider:()=>c,mdx:()=>x,useMDXComponents:()=>p,withMDXComponents:()=>m});var t=a(96540);function i(e,n,a){return n in e?Object.defineProperty(e,n,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[n]=a,e}function l(){return l=Object.assign||function(e){for(var n=1;n=0||(i[a]=e[a]);return i}(e,n);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(t=0;t=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var d=t.createContext({}),m=function(e){return function(n){var a=p(n.components);return t.createElement(e,l({},n,{components:a}))}},p=function(e){var n=t.useContext(d),a=n;return e&&(a="function"==typeof e?e(n):s(s({},n),e)),a},c=function(e){var n=p(e.components);return t.createElement(d.Provider,{value:n},e.children)},h="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return t.createElement(t.Fragment,{},n)}},f=t.forwardRef((function(e,n){var a=e.components,i=e.mdxType,l=e.originalType,r=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),m=p(a),c=i,h=m["".concat(r,".").concat(c)]||m[c]||u[c]||l;return a?t.createElement(h,s(s({ref:n},d),{},{components:a})):t.createElement(h,s({ref:n},d))}));function x(e,n){var a=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var l=a.length,r=new Array(l);r[0]=f;var s={};for(var o in n)hasOwnProperty.call(n,o)&&(s[o]=n[o]);s.originalType=e,s[h]="string"==typeof e?e:i,r[1]=s;for(var d=2;d{a.r(n),a.d(n,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>r,metadata:()=>o,toc:()=>m});var t=a(58168),i=(a(96540),a(15680)),l=a(14423);const r={id:"guide",title:"Angle Guide",sidebar_label:"Guide"},s=void 0,o={unversionedId:"angle/guide",id:"angle/guide",title:"Angle Guide",description:"The following guide will explain Angle from first principles, leading you through from simple queries to more complex ones.",source:"@site/docs/angle/guide.md",sourceDirName:"angle",slug:"/angle/guide",permalink:"/docs/angle/guide",draft:!1,editUrl:"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/angle/guide.md",tags:[],version:"current",frontMatter:{id:"guide",title:"Angle Guide",sidebar_label:"Guide"},sidebar:"someSidebar",previous:{title:"Introduction",permalink:"/docs/angle/intro"},next:{title:"Query Efficiency",permalink:"/docs/angle/efficiency"}},d={},m=[{value:"Just the facts",id:"just-the-facts",level:2},{value:"Matching nested facts",id:"matching-nested-facts",level:2},{value:"Union types",id:"union-types",level:2},{value:"Maybe",id:"maybe",level:2},{value:"Choice",id:"choice",level:2},{value:"Variables and more complex queries",id:"variables-and-more-complex-queries",level:2},{value:"Statements",id:"statements",level:2},{value:"Dot syntax",id:"dot-syntax",level:2},{value:"Dot syntax for union types",id:"dot-syntax-for-union-types",level:3},{value:"Extracting the key of a fact",id:"extracting-the-key-of-a-fact",level:3},{value:"If-then-else",id:"if-then-else",level:2},{value:"Arrays",id:"arrays",level:2},{value:"Sets",id:"sets",level:2},{value:"String prefix",id:"string-prefix",level:2},{value:"Tuples",id:"tuples",level:2},{value:"Enums and bool",id:"enums-and-bool",level:2},{value:"Negation",id:"negation",level:2}],p={toc:m},c="wrapper";function h(e){let{components:n,...a}=e;return(0,i.mdx)(c,(0,t.A)({},p,a,{components:n,mdxType:"MDXLayout"}),(0,i.mdx)("p",null,"The following guide will explain Angle from first principles, leading you through from simple queries to more complex ones."),(0,i.mdx)("p",null,"If you want to try the examples for yourself, or experiment with\nchanges to the example schema, you should first follow the\ninstructions in ",(0,i.mdx)("a",{parentName:"p",href:"/docs/walkthrough"},"Walkthrough")," to get set up."),(0,i.mdx)(l.FbInternalOnly,{mdxType:"FbInternalOnly"},(0,i.mdx)("p",null,"There are also ",(0,i.mdx)("a",{parentName:"p",href:"https://www.internalfb.com/intern/wiki/Glean/Query/Angle/Angle_examples/"},"examples of using Angle")," to query real data.")),(0,i.mdx)("h2",{id:"just-the-facts"},"Just the facts"),(0,i.mdx)("p",null,"Data in Glean is described by a ",(0,i.mdx)("em",{parentName:"p"},"schema"),", which we normally put in a file with the extension ",(0,i.mdx)("inlineCode",{parentName:"p"},"angle"),". For the purposes of this guide we\u2019ll use the example schema in ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.angle"),". Full details about defining schemas can be found in ",(0,i.mdx)("a",{parentName:"p",href:"/docs/schema/basic"},"Schemas"),". The ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.angle")," file contains a schema definition like this:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"schema example.1 {\n\n# definitions go here\n\n}\n")),(0,i.mdx)("p",null,"This says we\u2019re defining a schema called ",(0,i.mdx)("inlineCode",{parentName:"p"},"example"),", with version 1."),(0,i.mdx)("p",null,"The schema contains definitions for ",(0,i.mdx)("em",{parentName:"p"},"predicates"),". A predicate is the type of ",(0,i.mdx)("em",{parentName:"p"},"facts"),", which are the individual pieces of information that Glean stores. Our example schema models a simplified class hierarchy for an object-oriented language, starting with a predicate for a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Class :\n {\n name : string,\n line : nat,\n }\n")),(0,i.mdx)("p",null,"This says that the facts of ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class")," are records with two fields, a ",(0,i.mdx)("inlineCode",{parentName:"p"},"name")," field which contains a ",(0,i.mdx)("inlineCode",{parentName:"p"},"string"),", and a ",(0,i.mdx)("inlineCode",{parentName:"p"},"line")," field which contains a ",(0,i.mdx)("inlineCode",{parentName:"p"},"nat")," (\u201cnat\u201d is short for \u201cnatural number\u201d, which is limited to 64 bits in Glean)."),(0,i.mdx)("p",null,"The simplest type of Angle query is one that just selects facts from\nthe database that match a pattern. For our first Angle query, let\u2019s\nfind a class by its name:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'facts> example.Class { name = "Pet" }\n{ "id": 1024, "key": { "name": "Pet", "line": 10 } }\n\n1 results, 1 facts, 4.61ms, 117632 bytes, 677 compiled bytes\n')),(0,i.mdx)("p",null,"(The last line contains statistics about query performance from Glean; I\u2019ll leave this out in the rest of the examples.)"),(0,i.mdx)("p",null,"What\u2019s going on here?"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"The query consists of the ",(0,i.mdx)("em",{parentName:"li"},"predicate name")," ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Class")," followed by a ",(0,i.mdx)("em",{parentName:"li"},"pattern")," ",(0,i.mdx)("inlineCode",{parentName:"li"},'{ name = "Pet" }')),(0,i.mdx)("li",{parentName:"ul"},"Note that when we refer to a predicate in a query, the name is ",(0,i.mdx)("em",{parentName:"li"},"qualified")," by prefixing the schema name, so it\u2019s ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Class")," rather than just ",(0,i.mdx)("inlineCode",{parentName:"li"},"Class"),"."),(0,i.mdx)("li",{parentName:"ul"},"The query returns all the facts of ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Class")," that match the pattern")),(0,i.mdx)("p",null,"The shell shows results in JSON format. When you\u2019re making Glean\nqueries from code, the results will normally be decoded into native\ndata types that you can manipulate directly in whatever language\nyou\u2019re using; for more details see ",(0,i.mdx)("a",{parentName:"p",href:"/docs/schema/thrift"},"Thrift and\nJSON"),"."),(0,i.mdx)("p",null,"Note that each fact has a unique ",(0,i.mdx)("inlineCode",{parentName:"p"},"id"),". This is how Glean identifies facts in its database. As a user you normally won\u2019t have to worry about fact ",(0,i.mdx)("inlineCode",{parentName:"p"},"id"),"s; you can think of them like memory addresses."),(0,i.mdx)("p",null,"The pattern specifies which facts to return. In the example above, our pattern is matching a record type and specifying a subset of the fields: just the ",(0,i.mdx)("inlineCode",{parentName:"p"},"name")," field. We could match the ",(0,i.mdx)("inlineCode",{parentName:"p"},"line")," field instead:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=sh"},'facts> example.Class { line = 20 }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n')),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"Your patterns should normally match fields at the ",(0,i.mdx)("em",{parentName:"p"},"beginning")," of the\nrecord, because facts in the database are indexed by a prefix of the\nfields. Matching a field in the middle of the record works by scanning\nall the facts, which could be expensive. We\u2019ll get into this in more\ndetail in ",(0,i.mdx)("a",{parentName:"p",href:"/docs/angle/efficiency"},"Query Efficiency"),".")),(0,i.mdx)("p",null,'What other kinds of patterns can we use? Well, the simplest patterns are the wildcard, \u201c_\u201d, which matches anything, and "never", which always fails to match.'),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Class _\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n{ "id": 1027, "key": { "name": "Goldfish", "line": 40 } }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n{ "id": 1024, "key": { "name": "Pet", "line": 10 } }\nfacts> example.Class never\n(no results)\n')),(0,i.mdx)("p",null,"We\u2019ll introduce more kinds of pattern in the following sections. The full list of patterns can be found in ",(0,i.mdx)("a",{parentName:"p",href:"/docs/angle/reference"},"Angle Reference"),"."),(0,i.mdx)("h2",{id:"matching-nested-facts"},"Matching nested facts"),(0,i.mdx)("p",null,"The real power of Glean comes from relationships between facts. Facts can refer directly to other facts, and we can write queries that directly match on these connections."),(0,i.mdx)("p",null,"Our example schema has a predicate that expresses the inheritance relationship between classes:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Parent :\n {\n child : Class,\n parent : Class,\n }\n")),(0,i.mdx)("p",null,"Let\u2019s find what ",(0,i.mdx)("inlineCode",{parentName:"p"},"Fish")," inherits from:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Parent { child = { name = "Fish" }}\n{\n "id": 1029,\n "key": { "child": { "id": 1026, "key": { "name": "Fish", "line": 30 } }, "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } } }\n}\n')),(0,i.mdx)("p",null,"Let\u2019s break this down."),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},(0,i.mdx)("inlineCode",{parentName:"li"},'{ child = { name = "Fish" }}')," is a pattern that matches the key type of ",(0,i.mdx)("inlineCode",{parentName:"li"},"Parent")),(0,i.mdx)("li",{parentName:"ul"},"So, looking at the schema, ",(0,i.mdx)("inlineCode",{parentName:"li"},'{ name = "Fish" }')," is a pattern that should match the ",(0,i.mdx)("inlineCode",{parentName:"li"},"Class")," in the field ",(0,i.mdx)("inlineCode",{parentName:"li"},"child"),".")),(0,i.mdx)("p",null,"By default Angle queries recursively expand facts in the results. We can see in the above result that the ",(0,i.mdx)("inlineCode",{parentName:"p"},"child")," and ",(0,i.mdx)("inlineCode",{parentName:"p"},"parent")," fields contain the full facts they point to. If we want the result to be \u201cshallow\u201d, meaning it contains just the facts that match and not the nested facts, we can ask Glean to not expand the content of those references. In the shell this is done by running the command ",(0,i.mdx)("inlineCode",{parentName:"p"},":expand off"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> :expand off\nfacts> example.Parent { child = { name = "Fish" }}\n{ "id": 1029, "key": { "child": { "id": 1026 }, "parent": { "id": 1024 } } }\n')),(0,i.mdx)("p",null,"We can of course go the other way and find all the children of a class:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Parent { parent = { name = "Pet" }}\n{\n "id": 1028,\n "key": {\n "child": { "id": 1025, "key": { "name": "Lizard", "line": 20 } },\n "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } }\n }\n}\n{\n "id": 1029,\n "key": {\n "child": { "id": 1026, "key": { "name": "Fish", "line": 30 } },\n "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } }\n }\n}\n')),(0,i.mdx)("p",null,"But as before, note that this would be an inefficient query if we had a lot of data because the pattern is matching on the second field of ",(0,i.mdx)("inlineCode",{parentName:"p"},"Parent")," (namely ",(0,i.mdx)("inlineCode",{parentName:"p"},"parent"),"). Later we\u2019ll see how to make these queries more efficient using a derived predicate."),(0,i.mdx)("h2",{id:"union-types"},"Union types"),(0,i.mdx)("p",null,"Our examples so far have dealt with record types. Glean also supports ",(0,i.mdx)("em",{parentName:"p"},"union types"),", also called ",(0,i.mdx)("em",{parentName:"p"},"sum types"),", which are used to express multiple alternatives. For example, let\u2019s expand our schema to include class members which can be either a method or a variable:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Has :\n {\n class_ : Class,\n has : Member,\n access : enum { public_ | private_ },\n }\n\npredicate Member :\n {\n method : { name : string, doc : maybe string } |\n variable : { name : string }\n }\n")),(0,i.mdx)("p",null,"The predicate ",(0,i.mdx)("inlineCode",{parentName:"p"},"Has")," maps a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class")," to a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Member")," (with a ",(0,i.mdx)("inlineCode",{parentName:"p"},"public_")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"private_")," annotation), and a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Member")," is either ",(0,i.mdx)("inlineCode",{parentName:"p"},"method")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"variable"),", with some associated data. Note that a ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class")," might have more than one ",(0,i.mdx)("inlineCode",{parentName:"p"},"Member"),", which is fine: there can be multiple ",(0,i.mdx)("inlineCode",{parentName:"p"},"Has")," facts for a given ",(0,i.mdx)("inlineCode",{parentName:"p"},"Class"),"."),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"The schema uses ",(0,i.mdx)("inlineCode",{parentName:"p"},"class_")," rather than ",(0,i.mdx)("inlineCode",{parentName:"p"},"class")," as a field name, because\n",(0,i.mdx)("inlineCode",{parentName:"p"},"class")," is a reserved word in Angle. Similarly, we added trailing\nunderscores to ",(0,i.mdx)("inlineCode",{parentName:"p"},"public_")," and",(0,i.mdx)("inlineCode",{parentName:"p"},"private_")," for the same reason."),(0,i.mdx)("p",{parentName:"admonition"},"There are many such reserved words, which are reserved not because Angle uses them, but because they cause problems for code that is automatically generated from the schema. To avoid having too many ad-hoc language-specific naming rules, Glean prevents certain problematic names from being used in the schema. The Angle compiler will tell you if you try to use a reserved word.")),(0,i.mdx)("p",null,"Let\u2019s find classes that have a variable called ",(0,i.mdx)("inlineCode",{parentName:"p"},"fins"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Has { has = { variable = { name = "fins" }}}\n{\n "id": 1036,\n "key": {\n "class_": { "id": 1026, "key": { "name": "Fish", "line": 30 } },\n "has": { "id": 1035, "key": { "variable": { "name": "fins" } } },\n "access": 1\n }\n}\n')),(0,i.mdx)("p",null,"The key thing here is that we matched on ",(0,i.mdx)("inlineCode",{parentName:"p"},"Member")," which is a union type, using the pattern ",(0,i.mdx)("inlineCode",{parentName:"p"},'{ variable = { name = "fins" }}'),". A pattern to match a union type looks very much like a record pattern, but it can have only a single field, in this case either ",(0,i.mdx)("inlineCode",{parentName:"p"},"variable")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"method"),"."),(0,i.mdx)("h2",{id:"maybe"},"Maybe"),(0,i.mdx)("p",null,"Glean has one built-in union type called ",(0,i.mdx)("inlineCode",{parentName:"p"},"maybe"),", which is useful when we want to have optional values in the data. It's used in our example schema to attach optional documentation to a class member:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Member :\n {\n method : { name : string, doc : maybe string } |\n variable : { name : string }\n }\n")),(0,i.mdx)("p",null,"The type ",(0,i.mdx)("inlineCode",{parentName:"p"},"maybe string")," behaves exactly as if it were defined as the union type ",(0,i.mdx)("inlineCode",{parentName:"p"},"{ nothing | just : string }"),". That means we can write a pattern that matches it, exactly as we would write a pattern for ",(0,i.mdx)("inlineCode",{parentName:"p"},"{ nothing | just : string }"),":"),(0,i.mdx)("p",null,"Methods without documentation:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"facts> example.Member { method = { doc = nothing } }\n")),(0,i.mdx)("p",null,"Methods with documentation:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"facts> example.Member { method = { doc = { just = _ }}}\n")),(0,i.mdx)("h2",{id:"choice"},"Choice"),(0,i.mdx)("p",null,"In a pattern we can express multiple alternatives by separating patterns with a vertical bar ",(0,i.mdx)("inlineCode",{parentName:"p"},"|"),"."),(0,i.mdx)("p",null,"For example, we can find classes on lines 20 or 30:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Class { line = 20 | 30 }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n')),(0,i.mdx)("p",null,"Or we can find all the classes that have either a ",(0,i.mdx)("inlineCode",{parentName:"p"},"method")," called ",(0,i.mdx)("inlineCode",{parentName:"p"},"feed")," or a ",(0,i.mdx)("inlineCode",{parentName:"p"},"variable")," with any name:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Has { has = { method = { name = "feed" }} | { variable = _ }}\n\n(results omitted)\n')),(0,i.mdx)("h2",{id:"variables-and-more-complex-queries"},"Variables and more complex queries"),(0,i.mdx)("p",null,"So far we\u2019ve seen how to query for facts by matching patterns, including matching nested facts. In this section we\u2019ll see how to construct more complex queries that combine matching facts from multiple predicates."),(0,i.mdx)("p",null,"Suppose we want to find all the parents of classes that have a variable called ",(0,i.mdx)("inlineCode",{parentName:"p"},"fins"),". We need to build a query that will"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"find the classes with a variable called ",(0,i.mdx)("inlineCode",{parentName:"li"},"fins")," using ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Has")," as we did above"),(0,i.mdx)("li",{parentName:"ul"},"find their parents using ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Parent"))),(0,i.mdx)("p",null,"We can combine these two as follows:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'example.Has\n {\n class_ = C,\n has = { variable = { name = "fins" }}\n };\nexample.Parent { child = C }\n')),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"I\u2019ve written this on several lines with indentation to illustrate it\nbetter, to do this in the shell you will need to use the ",(0,i.mdx)("inlineCode",{parentName:"p"},":edit"),"\ncommand to put the query in a temporary file.")),(0,i.mdx)("p",null,"The key thing here is that we used a ",(0,i.mdx)("em",{parentName:"p"},"variable")," ",(0,i.mdx)("inlineCode",{parentName:"p"},"C")," to stand for the ",(0,i.mdx)("inlineCode",{parentName:"p"},"class_")," field when matching facts of ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.Has"),", and then we searched for ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.Parent")," facts with the same value of ",(0,i.mdx)("inlineCode",{parentName:"p"},"C")," for the ",(0,i.mdx)("inlineCode",{parentName:"p"},"child")," field."),(0,i.mdx)("p",null,"Note that variables must ",(0,i.mdx)("em",{parentName:"p"},"always")," begin with an upper-case letter, while schema names (",(0,i.mdx)("inlineCode",{parentName:"p"},"example)")," and field names (",(0,i.mdx)("inlineCode",{parentName:"p"},"child"),") begin with a lower-case letter."),(0,i.mdx)("p",null,"The semicolon separates multiple ",(0,i.mdx)("em",{parentName:"p"},"statements")," in a query. When there are multiple statements the results of the query are the facts that match the last statement, in this case the ",(0,i.mdx)("inlineCode",{parentName:"p"},"example.Parent"),". Let\u2019s try it:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Has { class_ = C, has = { variable = { name = "fins" }}}; example.Parent { child = C }\n{\n "id": 1029,\n "key": {\n "child": { "id": 1026, "key": { "name": "Fish", "line": 30 } },\n "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } }\n }\n}\n')),(0,i.mdx)("p",null," Suppose we don\u2019t care too much about the child here, we only care about getting a list of the parents. We can avoid returning the redundant information by specifying explicitly what it is we want to return from the query:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'P where\n example.Has\n {\n class_ = C,\n has = { variable = { name = "fins" }}\n };\n example.Parent { child = C, parent = P }\n')),(0,i.mdx)("p",null,"The general form of the query is ",(0,i.mdx)("em",{parentName:"p"},(0,i.mdx)("inlineCode",{parentName:"em"},"expression"))," ",(0,i.mdx)("inlineCode",{parentName:"p"},"where")," ",(0,i.mdx)("em",{parentName:"p"},(0,i.mdx)("inlineCode",{parentName:"em"},"statements")),", where ",(0,i.mdx)("em",{parentName:"p"},(0,i.mdx)("inlineCode",{parentName:"em"},"expression"))," is an arbitrary expression and each statement is a pattern that matches some facts. The results of the query are the distinct values of ",(0,i.mdx)("em",{parentName:"p"},(0,i.mdx)("inlineCode",{parentName:"em"},"expression"))," for which all the statements match facts in the database."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> P where example.Has { class_ = C, has = { variable = { name = "fins" }}}; example.Parent { child = C, parent = P }\n{ "id": 1024, "key": { "name": "Pet", "line": 10 } }\n')),(0,i.mdx)("h2",{id:"statements"},"Statements"),(0,i.mdx)("p",null,"In general, a statement can be of the form ",(0,i.mdx)("em",{parentName:"p"},"A = B.")," For example, if we write"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'C = example.Class { name = "Fish" };\nexample.Parent { child = C }\n')),(0,i.mdx)("p",null,"that\u2019s the same as"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'example.Parent { child = { name = "Fish" }}\n')),(0,i.mdx)("p",null,"A statement can have a pattern on either side, for example"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'C where\n C = example.Class { name = N };\n N = "Fish" | "Goldfish"\n')),(0,i.mdx)("p",null,"A statement can itself be a set of alternatives separated by a vertical bar ",(0,i.mdx)("inlineCode",{parentName:"p"},"|"),". For example, we can find classes that are either a parent of the ",(0,i.mdx)("inlineCode",{parentName:"p"},"Goldfish")," or have a ",(0,i.mdx)("inlineCode",{parentName:"p"},"feed")," method:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'C where\n example.Parent { child = { name = "Goldfish" }, parent = C } |\n example.Has { class_ = C, has = { method = { name = "feed" }}}\n')),(0,i.mdx)("h2",{id:"dot-syntax"},"Dot syntax"),(0,i.mdx)("p",null,'So far we\'ve been extracting fields from records by writing patterns,\nbut we can also extract fields from records using the traditional "dot\nsyntax". For example, instead of'),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'example.Parent { child = { name = "Fish" }}\n')),(0,i.mdx)("p",null,"we could write"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'example.Parent P where P.child.name = "Fish"\n')),(0,i.mdx)("p",null,"Here"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},(0,i.mdx)("inlineCode",{parentName:"li"},"example.Parent P")," selects facts of ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Parent")," and binds the key to the variable ",(0,i.mdx)("inlineCode",{parentName:"li"},"P")),(0,i.mdx)("li",{parentName:"ul"},(0,i.mdx)("inlineCode",{parentName:"li"},'P.child.name = "Fish"')," is a constraint on the ",(0,i.mdx)("inlineCode",{parentName:"li"},"name")," field of the ",(0,i.mdx)("inlineCode",{parentName:"li"},"child")," field of ",(0,i.mdx)("inlineCode",{parentName:"li"},"P")),(0,i.mdx)("li",{parentName:"ul"},"the query returns all facts of ",(0,i.mdx)("inlineCode",{parentName:"li"},"example.Parent")," that satisfy the constraint")),(0,i.mdx)("p",null,"Dot syntax tends to be more concise than patterns when there are\ndeeply-nested records, because it avoids all the nested braces."),(0,i.mdx)("h3",{id:"dot-syntax-for-union-types"},"Dot syntax for union types"),(0,i.mdx)("p",null,"Matching union types can also be achieved using dot syntax. For\nexample, earlier we had"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'example.Has { has = { variable = { name = "fins" }}}\n')),(0,i.mdx)("p",null,"using dot syntax this would be"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'example.Has H where H.has.variable?.name = "fins"\n')),(0,i.mdx)("p",null,"Note that when selecting a union type we add a '?' suffix,\nas with ",(0,i.mdx)("inlineCode",{parentName:"p"},".variable?")," in the example above. This makes it more obvious\nthat we're doing something conditional: if ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.has")," is not a\n",(0,i.mdx)("inlineCode",{parentName:"p"},"variable"),", then ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.has.variable?")," has no values."),(0,i.mdx)("p",null,"Selecting from union types works nicely with choice (",(0,i.mdx)("inlineCode",{parentName:"p"},"|"),"):"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"Name where\n example.Has H;\n Name = (H.has.variable?.name | H.has.method?.name)\n")),(0,i.mdx)("p",null,"returns all the names of variables and methods."),(0,i.mdx)("h3",{id:"extracting-the-key-of-a-fact"},"Extracting the key of a fact"),(0,i.mdx)("p",null,"It's sometimes useful to be able to extract the key of a fact. If we\nhave a variable ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," of some predicate type, then we can extract the\nkey of ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," with ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.*"),". If ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," has type ",(0,i.mdx)("inlineCode",{parentName:"p"},"P")," and ",(0,i.mdx)("inlineCode",{parentName:"p"},"P")," is a predicate with\nkey type ",(0,i.mdx)("inlineCode",{parentName:"p"},"K"),", then ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.*")," has type ",(0,i.mdx)("inlineCode",{parentName:"p"},"K"),"."),(0,i.mdx)("p",null,"Usually we don't need to extract the key explicitly because Angle does\nit automatically. For example if ",(0,i.mdx)("inlineCode",{parentName:"p"},"X : example.Class"),", then ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.name")," is\nshorthand for ",(0,i.mdx)("inlineCode",{parentName:"p"},"X.*.name"),". But sometimes we just want the key without\nselecting a field, or perhaps the key isn't a record. In those cases,\n",(0,i.mdx)("inlineCode",{parentName:"p"},"X.*")," can be useful."),(0,i.mdx)("h2",{id:"if-then-else"},"If-then-else"),(0,i.mdx)("p",null,"We can conditionally match patterns using ",(0,i.mdx)("inlineCode",{parentName:"p"},"if then else"),"."),(0,i.mdx)("p",null,"Variables matched in the condition will be available in the ",(0,i.mdx)("inlineCode",{parentName:"p"},"then")," branch."),(0,i.mdx)("p",null,"Whilst a choice will always evaluate both of its branches, the ",(0,i.mdx)("inlineCode",{parentName:"p"},"else")," branch of an if will\nnever be evaluated if the condition succeeds at least once."),(0,i.mdx)("p",null,"For example, we could get all child classes if inheritance is being used in the codebase, or\nretrieve all classes if it isn't."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},'facts > if (example.Parent { child = X }) then X else example.Class _\n { "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n { "id": 1026, "key": { "name": "Fish", "line": 30 } }\n { "id": 1027, "key": { "name": "Goldfish", "line": 40 } }\n')),(0,i.mdx)("p",null,"Please note that ",(0,i.mdx)("inlineCode",{parentName:"p"},"if")," cannot be used in stored derived predicates. This\nis the case because they require the use of negation, which is disallowed in\nstored predicates."),(0,i.mdx)("h2",{id:"arrays"},"Arrays"),(0,i.mdx)("p",null,"When the schema uses an array, we need to be able to write queries that traverse the elements of the array. For example, a common use of an array is to represent the list of declarations in a source file. Our example schema defines the ",(0,i.mdx)("inlineCode",{parentName:"p"},"FileClasses")," predicate:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate FileClasses :\n {\n file : string,\n classes : [Class]\n }\n")),(0,i.mdx)("p",null,"The goal here is to map efficiently from a filename to the list of classes defined in that file. Suppose we want to write a query that finds all the classes called ",(0,i.mdx)("inlineCode",{parentName:"p"},"Goldfish")," in the file ",(0,i.mdx)("inlineCode",{parentName:"p"},"petshop.example"),", we could do it like this:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'example.FileClasses { file = "petshop.example", classes = Cs };\n{ name = "Goldfish" } = Cs[..]\n')),(0,i.mdx)("p",null,"The second line is the interesting one: ",(0,i.mdx)("inlineCode",{parentName:"p"},'{ name = "Goldfish" } = Cs[..]')," means"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"on the right-hand side, ",(0,i.mdx)("inlineCode",{parentName:"li"},"Cs[..]")," means \u201ceach element of the array ",(0,i.mdx)("inlineCode",{parentName:"li"},"Cs"),"\u201d"),(0,i.mdx)("li",{parentName:"ul"},"the left-hand side is a pattern, filtering only those ",(0,i.mdx)("inlineCode",{parentName:"li"},"Class")," facts that match ",(0,i.mdx)("inlineCode",{parentName:"li"},'{ name = "Goldfish" }'))),(0,i.mdx)("p",null,"We can also match the whole array with a pattern of the form ",(0,i.mdx)("inlineCode",{parentName:"p"},"[ p1, p2, .. ]")),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> X where [_,X,_] = [1,2,3]\n{ "id": 1040, "key": 2 }\n')),(0,i.mdx)("p",null,"Or if we don't care about the length of the array:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> X where [_,X, ..] = [1,2,3]\n{ "id": 1040, "key": 2 }\n')),(0,i.mdx)("h2",{id:"sets"},"Sets"),(0,i.mdx)("p",null,"Sets are similar to arrays but helpful when the order of the elements are not important and duplicates are also irrelevant.\nA common example is when storing cross references. For instance, the python schema has a predicate which contains all\nname cross references in a file. The cross references are currently stored in an array but it could be stored in a set as below."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate XRefsViaNameByFile:\n {\n file: src.File,\n xrefs: set XRefViaName,\n }\n")),(0,i.mdx)("p",null,"If we want to know for a particular file and a particular name, where it is used we could write the following query:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'XRefsViaNameByFile { file = "foo.py", xrefs = XRefs };\n{ target = { name = "Bar" } } = elements XRefs\n')),(0,i.mdx)("p",null,"The second line uses the construct ",(0,i.mdx)("inlineCode",{parentName:"p"},"elements")," which is similar to the ",(0,i.mdx)("inlineCode",{parentName:"p"},"[..]")," syntax for arrays."),(0,i.mdx)("p",null,"We can also create new sets from the results of a query. This is done using the ",(0,i.mdx)("inlineCode",{parentName:"p"},"all")," construct. For instance\n",(0,i.mdx)("inlineCode",{parentName:"p"},"all (1 | 2 | 3)")," is a set containing the number ",(0,i.mdx)("inlineCode",{parentName:"p"},"1"),", ",(0,i.mdx)("inlineCode",{parentName:"p"},"2"),", and ",(0,i.mdx)("inlineCode",{parentName:"p"},"3"),"."),(0,i.mdx)("p",null,"The ",(0,i.mdx)("inlineCode",{parentName:"p"},"all")," construct can be used in combination with the ",(0,i.mdx)("inlineCode",{parentName:"p"},"elements")," construct to, for instance, map over a set\nof elements and transform them. In the example below, the second line takes each element of the ",(0,i.mdx)("inlineCode",{parentName:"p"},"StringSet")," and\napplies the primitive ",(0,i.mdx)("inlineCode",{parentName:"p"},"prim.toLower")," to it. The result is a set where all the strings are lowercase."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'StringSet = all ("Foo" | "Bar" | "Baz" );\nall (String = elements StringSet; prim.toLower String)\n')),(0,i.mdx)("h2",{id:"string-prefix"},"String prefix"),(0,i.mdx)("p",null,"We\u2019ve seen many examples of patterns that match strings. Glean also supports matching strings by ",(0,i.mdx)("em",{parentName:"p"},"prefix"),"; for example:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Class { name = "F".. }\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n')),(0,i.mdx)("p",null,"The syntax ",(0,i.mdx)("inlineCode",{parentName:"p"},'"F"..')," means ",(0,i.mdx)("em",{parentName:"p"},"strings beginning with the prefix")," ",(0,i.mdx)("inlineCode",{parentName:"p"},'\u201dF"'),"."),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"Why only prefix and not substring matching in general? Prefix matching can be supported efficiently by Glean\u2019s prefix-tree representation of the fact database. Other kinds of string matching could be supported, but they wouldn\u2019t be able to exploit the database representation so there\u2019s little advantage to implementing them in Angle compared with filtering on the client-side.")),(0,i.mdx)("h2",{id:"tuples"},"Tuples"),(0,i.mdx)("p",null,"A ",(0,i.mdx)("em",{parentName:"p"},"tuple")," is just a a way of writing a record without the field names. So for example, instead of"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"example.Parent { child = C }\n")),(0,i.mdx)("p",null,"we could write"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre"},"example.Parent { C, _ }\n")),(0,i.mdx)("p",null,"When using a tuple you have to list ",(0,i.mdx)("em",{parentName:"p"},"all")," the fields, in the same order as they are declared in the schema. That's why ",(0,i.mdx)("inlineCode",{parentName:"p"},"{ child = C }")," becomes ",(0,i.mdx)("inlineCode",{parentName:"p"},"{ C, _ }")," when written as a tuple."),(0,i.mdx)("p",null,"There are upsides and downsides to using the tuple notation:"),(0,i.mdx)("ul",null,(0,i.mdx)("li",{parentName:"ul"},"Pro: more concise"),(0,i.mdx)("li",{parentName:"ul"},"Con: brittle and sensitive to changes in the schema. If we add a field, then tuple patterns will break whereas record patterns won't.")),(0,i.mdx)("p",null,'As a rule of thumb we tend to use tuple syntax in cases where the predicate is "obviously" a relation, such as ',(0,i.mdx)("inlineCode",{parentName:"p"},"example.Parent"),", but we wouldn't use tuple syntax for more complex records."),(0,i.mdx)("h2",{id:"enums-and-bool"},"Enums and bool"),(0,i.mdx)("p",null,"An ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," type is a set of named constants. In the ",(0,i.mdx)("inlineCode",{parentName:"p"},"Has")," predicate we used an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," type to indicate whether a class member is ",(0,i.mdx)("inlineCode",{parentName:"p"},"public_")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"private_"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"predicate Has :\n {\n class_ : Class,\n has : Member,\n access : enum { public_ | private_ },\n }\n")),(0,i.mdx)("p",null,"To match an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," we just use the appropriate identifier, in this case ",(0,i.mdx)("inlineCode",{parentName:"p"},"public_")," or ",(0,i.mdx)("inlineCode",{parentName:"p"},"private"),":"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> example.Has { access = private_ }\n{ "id": 1036, "key": { "class_": { "id": 1026 }, "has": { "id": 1035 }, "access": 1 } }\n')),(0,i.mdx)("p",null,"Note that in the JSON format results, an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," is represented by an integer. When you make queries in code, the ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," will be represented by an appropriate type, such as a ",(0,i.mdx)("inlineCode",{parentName:"p"},"data")," type in Haskell."),(0,i.mdx)("p",null,"The boolean type ",(0,i.mdx)("inlineCode",{parentName:"p"},"bool")," is a special case of an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum"),", defined like this:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},"type bool = enum { false | true }\n")),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"Normally the constants of an ",(0,i.mdx)("inlineCode",{parentName:"p"},"enum")," should begin with a lower case\nletter. You can use constants beginning with an upper-case letter, but\nto distinguish the constant from a variable name you may sometimes\nneed to provide a type signature, e.g. ",(0,i.mdx)("inlineCode",{parentName:"p"},"Constant : Type")," rather than\njust ",(0,i.mdx)("inlineCode",{parentName:"p"},"Constant"),".")),(0,i.mdx)("h2",{id:"negation"},"Negation"),(0,i.mdx)("p",null,"If we want results that do not match a certain criterion, we can use ",(0,i.mdx)("inlineCode",{parentName:"p"},"!")," to\nspecify a subquery that should fail. A subquery fails if it doesn't return any\nresult."),(0,i.mdx)("p",null,"For example, we can find classes that don't have methods"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> C where C = example.Class _; !(example.Has { class_ = C, has = { method = _ } })\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n{ "id": 1027, "key": { "name": "Goldfish", "line": 40 } }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n')),(0,i.mdx)("p",null,"Or we could find the maximum element in an array"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-lang=angle"},'facts> X where Values = [5,1,2,3]; X = Values[..]; !(Y = Values[..]; Y > X)\n{ "id": 1091, "key": 5 }\n')),(0,i.mdx)("p",null,"The query asks for the ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," for which given all values of ",(0,i.mdx)("inlineCode",{parentName:"p"},"Y")," ",(0,i.mdx)("em",{parentName:"p"},"none")," is greater\nthan it. If ",(0,i.mdx)("inlineCode",{parentName:"p"},"Y = Values[..]")," were outside of the negation, the meaning would\nbe give me all ",(0,i.mdx)("inlineCode",{parentName:"p"},"X")," for which there is ",(0,i.mdx)("em",{parentName:"p"},"at least one")," ",(0,i.mdx)("inlineCode",{parentName:"p"},"Y")," that is not greater\nthan it. The answer to that would be all elements."))}h.isMDXComponent=!0},80510:function(e,n,a){var t=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.getSpecInfo=void 0;const i=a(88266);n.getSpecInfo=function(e){return t(this,void 0,void 0,(function*(){return yield i.call({module:"bloks",api:"getSpecInfo",args:{styleId:e}})}))}},88266:function(e,n){var a=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.call=void 0;let t=!1,i=0;const l={};n.call=function(e){return a(this,void 0,void 0,(function*(){if("staticdocs.thefacebook.com"!==window.location.hostname&&"localhost"!==window.location.hostname)return Promise.reject(new Error("Not running on static docs"));t||(t=!0,window.addEventListener("message",(e=>{if("static-docs-bridge-response"!==e.data.event)return;const n=e.data.id;n in l||console.error(`Recieved response for id: ${n} with no matching receiver`),"response"in e.data?l[n].resolve(e.data.response):l[n].reject(new Error(e.data.error)),delete l[n]})));const n=i++,a=new Promise(((e,a)=>{l[n]={resolve:e,reject:a}})),r={event:"static-docs-bridge-call",id:n,module:e.module,api:e.api,args:e.args},s="localhost"===window.location.hostname?"*":"https://www.internalfb.com";return window.parent.postMessage(r,s),a}))}},70680:function(e,n,a){var t=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.reportFeatureUsage=n.reportContentCopied=void 0;const i=a(88266);n.reportContentCopied=function(e){return t(this,void 0,void 0,(function*(){const{textContent:n}=e;try{yield i.call({module:"feedback",api:"reportContentCopied",args:{textContent:n}})}catch(a){}}))},n.reportFeatureUsage=function(e){return t(this,void 0,void 0,(function*(){const{featureName:n,id:a}=e;console.log("used feature");try{yield i.call({module:"feedback",api:"reportFeatureUsage",args:{featureName:n,id:a}})}catch(t){}}))}},14423:function(e,n,a){var t=this&&this.__createBinding||(Object.create?function(e,n,a,t){void 0===t&&(t=a),Object.defineProperty(e,t,{enumerable:!0,get:function(){return n[a]}})}:function(e,n,a,t){void 0===t&&(t=a),e[t]=n[a]}),i=this&&this.__setModuleDefault||(Object.create?function(e,n){Object.defineProperty(e,"default",{enumerable:!0,value:n})}:function(e,n){e.default=n}),l=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var n={};if(null!=e)for(var a in e)"default"!==a&&Object.prototype.hasOwnProperty.call(e,a)&&t(n,e,a);return i(n,e),n};Object.defineProperty(n,"__esModule",{value:!0}),n.OssOnly=n.FbInternalOnly=n.getEphemeralDiffNumber=n.hasEphemeralDiffNumber=n.isInternal=n.validateFbContentArgs=n.fbInternalOnly=n.fbContent=n.inpageeditor=n.feedback=n.uidocs=n.bloks=void 0,n.bloks=l(a(80510)),n.uidocs=l(a(3730)),n.feedback=l(a(70680)),n.inpageeditor=l(a(45458));const r=["internal","external"];function s(e){return d(e),m()?"internal"in e?o(e.internal):[]:"external"in e?o(e.external):[]}function o(e){return"function"==typeof e?e():e}function d(e){if("object"!=typeof e)throw new Error(`fbContent() args must be an object containing keys: ${r}. Instead got ${e}`);if(!Object.keys(e).find((e=>r.find((n=>n===e)))))throw new Error(`No valid args found in ${JSON.stringify(e)}. Accepted keys: ${r}`);const n=Object.keys(e).filter((e=>!r.find((n=>n===e))));if(n.length>0)throw new Error(`Unexpected keys ${n} found in fbContent() args. Accepted keys: ${r}`)}function m(){try{return Boolean(!1)}catch(e){return console.log("process.env.FB_INTERNAL couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),!1}}function p(){try{return null}catch(e){return console.log("process.env.PHABRICATOR_DIFF_NUMBER couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),null}}n.fbContent=s,n.fbInternalOnly=function(e){return s({internal:e})},n.validateFbContentArgs=d,n.isInternal=m,n.hasEphemeralDiffNumber=function(){return Boolean(p())},n.getEphemeralDiffNumber=p,n.FbInternalOnly=function(e){return m()?e.children:null},n.OssOnly=function(e){return m()?null:e.children}},45458:function(e,n,a){var t=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.submitDiff=void 0;const i=a(88266);n.submitDiff=function(e){return t(this,void 0,void 0,(function*(){const{file_path:n,new_content:a,project_name:t,diff_number:l}=e;try{return yield i.call({module:"inpageeditor",api:"createPhabricatorDiffApi",args:{file_path:n,new_content:a,project_name:t,diff_number:l}})}catch(r){throw new Error(`Error occurred while trying to submit diff. Stack trace: ${r}`)}}))}},3730:function(e,n,a){var t=this&&this.__awaiter||function(e,n,a,t){return new(a||(a=Promise))((function(i,l){function r(e){try{o(t.next(e))}catch(n){l(n)}}function s(e){try{o(t.throw(e))}catch(n){l(n)}}function o(e){var n;e.done?i(e.value):(n=e.value,n instanceof a?n:new a((function(e){e(n)}))).then(r,s)}o((t=t.apply(e,n||[])).next())}))};Object.defineProperty(n,"__esModule",{value:!0}),n.getApi=n.docsets=void 0;const i=a(88266);n.docsets={BLOKS_CORE:"887372105406659"},n.getApi=function(e){return t(this,void 0,void 0,(function*(){const{name:n,framework:a,docset:t}=e;return yield i.call({module:"uidocs",api:"getApi",args:{name:n,framework:a,docset:t}})}))}}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.60d0e6fc.js b/assets/js/runtime~main.c254b465.js similarity index 94% rename from assets/js/runtime~main.60d0e6fc.js rename to assets/js/runtime~main.c254b465.js index 3839f336b..04fb0ba57 100644 --- a/assets/js/runtime~main.60d0e6fc.js +++ b/assets/js/runtime~main.c254b465.js @@ -1 +1 @@ -(()=>{"use strict";var e,d,a,c,f,t={},r={};function b(e){var d=r[e];if(void 0!==d)return d.exports;var a=r[e]={id:e,loaded:!1,exports:{}};return t[e].call(a.exports,a,a.exports,b),a.loaded=!0,a.exports}b.m=t,b.c=r,e=[],b.O=(d,a,c,f)=>{if(!a){var t=1/0;for(i=0;i=f)&&Object.keys(b.O).every((e=>b.O[e](a[o])))?a.splice(o--,1):(r=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[a,c,f]},b.n=e=>{var d=e&&e.__esModule?()=>e.default:()=>e;return b.d(d,{a:d}),d},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,b.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var f=Object.create(null);b.r(f);var t={};d=d||[null,a({}),a([]),a(a)];for(var r=2&c&&e;"object"==typeof r&&!~d.indexOf(r);r=a(r))Object.getOwnPropertyNames(r).forEach((d=>t[d]=()=>e[d]));return t.default=()=>e,b.d(f,t),f},b.d=(e,d)=>{for(var a in d)b.o(d,a)&&!b.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:d[a]})},b.f={},b.e=e=>Promise.all(Object.keys(b.f).reduce(((d,a)=>(b.f[a](e,d),d)),[])),b.u=e=>"assets/js/"+({15:"cfd71120",76:"283d7b21",98:"7c10977a",119:"d558f29a",488:"a81f2d1d",513:"476d6aec",515:"21418ead",979:"373392d9",1224:"14a2b9f8",1493:"eb83943b",1577:"90f022e0",1739:"2dc45ced",1991:"b2b675dd",2008:"5e677a75",2184:"d22a0e6a",2329:"f349a764",2419:"27315305",2634:"c4f5d8e4",2711:"9e4087bc",2804:"a5dc57e5",2894:"e72a3ded",2982:"f228e963",2984:"cc8d6d7f",3249:"ccc49370",4024:"246b6efd",4319:"b16bf7d2",4324:"391ef999",4363:"3e65d5b4",4487:"af32bd62",4813:"6875c492",4959:"5baf5c08",5016:"9d050fe4",5033:"41206b0e",5147:"581d6198",5894:"b2f554cd",5899:"a09c2993",6002:"473435fa",6119:"31fe93dc",6307:"5c4c46e6",6533:"432e378d",6621:"37fc9d46",6669:"33b5e0ca",6835:"abda9da4",7472:"814f3328",7483:"ca95d5ad",7643:"a6aa9e1f",7733:"0b5ee478",7941:"b0b0b448",8025:"2dfecbce",8055:"60691868",8209:"01a85c17",8262:"af94d498",8401:"17896441",8481:"b8b1253e",8581:"935f2afb",8606:"8293a772",8690:"2651e53d",8714:"1be78505",9114:"1a20bc57",9159:"c83e76ae",9267:"a7023ddc",9387:"6b032a97",9427:"31d6d5ed",9698:"ea32bb6f"}[e]||e)+"."+{15:"0e3424f6",76:"5e2f56ef",98:"62d627bb",119:"7aef164b",488:"04aa09c0",513:"40d25fa8",515:"a068d6b1",979:"af1d08b7",1224:"584980eb",1493:"f67b0724",1577:"786a77de",1739:"c57eab73",1774:"5aef21ac",1991:"7eab122b",2008:"5b8358ab",2184:"5a72a44e",2329:"8c461c82",2419:"33b16225",2634:"e7dbb636",2711:"5650a2bf",2804:"f911ce9c",2894:"314b6caa",2982:"4300764b",2984:"766feed5",3249:"4345b62e",4024:"b76e466d",4319:"7213ad76",4324:"fbc78807",4363:"f6fca71e",4487:"22886726",4813:"f50ab996",4959:"7c6395aa",5016:"b345a195",5033:"5fbe809e",5147:"d9a82116",5894:"38dfd45f",5899:"9f2bd6dd",6002:"dc105a24",6119:"e6643ee3",6307:"3b4667af",6533:"206c9773",6621:"3e683732",6669:"b5e28820",6835:"e8dedb6a",7472:"cd0a7a20",7483:"a7057d62",7643:"52906f0b",7733:"fc00aec3",7941:"2ae0b894",8025:"ff264481",8055:"c4c5408b",8209:"8a4d26a4",8262:"685909ea",8401:"e197501f",8481:"20d5e1fd",8581:"59aeca34",8606:"bfd857cd",8690:"a86bc727",8714:"3ec7ab82",9100:"5ce11220",9114:"540bd4bc",9159:"697a8fc3",9267:"db0474c2",9387:"aa3161ce",9427:"319d3a6f",9698:"20a7fbb6"}[e]+".js",b.miniCssF=e=>{},b.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),b.o=(e,d)=>Object.prototype.hasOwnProperty.call(e,d),c={},f="website:",b.l=(e,d,a,t)=>{if(c[e])c[e].push(d);else{var r,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{r.onerror=r.onload=null,clearTimeout(s);var f=c[e];if(delete c[e],r.parentNode&&r.parentNode.removeChild(r),f&&f.forEach((e=>e(a))),d)return d(a)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:r}),12e4);r.onerror=u.bind(null,r.onerror),r.onload=u.bind(null,r.onload),o&&document.head.appendChild(r)}},b.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),b.p="/",b.gca=function(e){return e={17896441:"8401",27315305:"2419",60691868:"8055",cfd71120:"15","283d7b21":"76","7c10977a":"98",d558f29a:"119",a81f2d1d:"488","476d6aec":"513","21418ead":"515","373392d9":"979","14a2b9f8":"1224",eb83943b:"1493","90f022e0":"1577","2dc45ced":"1739",b2b675dd:"1991","5e677a75":"2008",d22a0e6a:"2184",f349a764:"2329",c4f5d8e4:"2634","9e4087bc":"2711",a5dc57e5:"2804",e72a3ded:"2894",f228e963:"2982",cc8d6d7f:"2984",ccc49370:"3249","246b6efd":"4024",b16bf7d2:"4319","391ef999":"4324","3e65d5b4":"4363",af32bd62:"4487","6875c492":"4813","5baf5c08":"4959","9d050fe4":"5016","41206b0e":"5033","581d6198":"5147",b2f554cd:"5894",a09c2993:"5899","473435fa":"6002","31fe93dc":"6119","5c4c46e6":"6307","432e378d":"6533","37fc9d46":"6621","33b5e0ca":"6669",abda9da4:"6835","814f3328":"7472",ca95d5ad:"7483",a6aa9e1f:"7643","0b5ee478":"7733",b0b0b448:"7941","2dfecbce":"8025","01a85c17":"8209",af94d498:"8262",b8b1253e:"8481","935f2afb":"8581","8293a772":"8606","2651e53d":"8690","1be78505":"8714","1a20bc57":"9114",c83e76ae:"9159",a7023ddc:"9267","6b032a97":"9387","31d6d5ed":"9427",ea32bb6f:"9698"}[e]||e,b.p+b.u(e)},(()=>{var e={5354:0,1869:0};b.f.j=(d,a)=>{var c=b.o(e,d)?e[d]:void 0;if(0!==c)if(c)a.push(c[2]);else if(/^(1869|5354)$/.test(d))e[d]=0;else{var f=new Promise(((a,f)=>c=e[d]=[a,f]));a.push(c[2]=f);var t=b.p+b.u(d),r=new Error;b.l(t,(a=>{if(b.o(e,d)&&(0!==(c=e[d])&&(e[d]=void 0),c)){var f=a&&("load"===a.type?"missing":a.type),t=a&&a.target&&a.target.src;r.message="Loading chunk "+d+" failed.\n("+f+": "+t+")",r.name="ChunkLoadError",r.type=f,r.request=t,c[1](r)}}),"chunk-"+d,d)}},b.O.j=d=>0===e[d];var d=(d,a)=>{var c,f,t=a[0],r=a[1],o=a[2],n=0;if(t.some((d=>0!==e[d]))){for(c in r)b.o(r,c)&&(b.m[c]=r[c]);if(o)var i=o(b)}for(d&&d(a);n{"use strict";var e,d,a,c,f,t={},r={};function b(e){var d=r[e];if(void 0!==d)return d.exports;var a=r[e]={id:e,loaded:!1,exports:{}};return t[e].call(a.exports,a,a.exports,b),a.loaded=!0,a.exports}b.m=t,b.c=r,e=[],b.O=(d,a,c,f)=>{if(!a){var t=1/0;for(i=0;i=f)&&Object.keys(b.O).every((e=>b.O[e](a[o])))?a.splice(o--,1):(r=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[a,c,f]},b.n=e=>{var d=e&&e.__esModule?()=>e.default:()=>e;return b.d(d,{a:d}),d},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,b.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var f=Object.create(null);b.r(f);var t={};d=d||[null,a({}),a([]),a(a)];for(var r=2&c&&e;"object"==typeof r&&!~d.indexOf(r);r=a(r))Object.getOwnPropertyNames(r).forEach((d=>t[d]=()=>e[d]));return t.default=()=>e,b.d(f,t),f},b.d=(e,d)=>{for(var a in d)b.o(d,a)&&!b.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:d[a]})},b.f={},b.e=e=>Promise.all(Object.keys(b.f).reduce(((d,a)=>(b.f[a](e,d),d)),[])),b.u=e=>"assets/js/"+({15:"cfd71120",76:"283d7b21",98:"7c10977a",119:"d558f29a",488:"a81f2d1d",513:"476d6aec",515:"21418ead",979:"373392d9",1224:"14a2b9f8",1493:"eb83943b",1577:"90f022e0",1739:"2dc45ced",1991:"b2b675dd",2008:"5e677a75",2184:"d22a0e6a",2329:"f349a764",2419:"27315305",2634:"c4f5d8e4",2711:"9e4087bc",2804:"a5dc57e5",2894:"e72a3ded",2982:"f228e963",2984:"cc8d6d7f",3249:"ccc49370",4024:"246b6efd",4319:"b16bf7d2",4324:"391ef999",4363:"3e65d5b4",4487:"af32bd62",4813:"6875c492",4959:"5baf5c08",5016:"9d050fe4",5033:"41206b0e",5147:"581d6198",5894:"b2f554cd",5899:"a09c2993",6002:"473435fa",6119:"31fe93dc",6307:"5c4c46e6",6533:"432e378d",6621:"37fc9d46",6669:"33b5e0ca",6835:"abda9da4",7472:"814f3328",7483:"ca95d5ad",7643:"a6aa9e1f",7733:"0b5ee478",7941:"b0b0b448",8025:"2dfecbce",8055:"60691868",8209:"01a85c17",8262:"af94d498",8401:"17896441",8481:"b8b1253e",8581:"935f2afb",8606:"8293a772",8690:"2651e53d",8714:"1be78505",9114:"1a20bc57",9159:"c83e76ae",9267:"a7023ddc",9387:"6b032a97",9427:"31d6d5ed",9698:"ea32bb6f"}[e]||e)+"."+{15:"0e3424f6",76:"d61de8d0",98:"901c28ec",119:"7aef164b",488:"04aa09c0",513:"40d25fa8",515:"51b22fa5",979:"af1d08b7",1224:"584980eb",1493:"f67b0724",1577:"786a77de",1739:"c57eab73",1774:"5aef21ac",1991:"7eab122b",2008:"5b8358ab",2184:"5a72a44e",2329:"8c461c82",2419:"33b16225",2634:"e7dbb636",2711:"5650a2bf",2804:"f911ce9c",2894:"314b6caa",2982:"4300764b",2984:"766feed5",3249:"4345b62e",4024:"b76e466d",4319:"7213ad76",4324:"fbc78807",4363:"459eb74e",4487:"22886726",4813:"f50ab996",4959:"7c6395aa",5016:"b345a195",5033:"5fbe809e",5147:"d9a82116",5894:"38dfd45f",5899:"9f2bd6dd",6002:"dc105a24",6119:"e6643ee3",6307:"3b4667af",6533:"2c47b22a",6621:"3e683732",6669:"2540bb03",6835:"e8dedb6a",7472:"cd0a7a20",7483:"a7057d62",7643:"52906f0b",7733:"fc00aec3",7941:"2ae0b894",8025:"ff264481",8055:"c4c5408b",8209:"8a4d26a4",8262:"685909ea",8401:"e197501f",8481:"20d5e1fd",8581:"59aeca34",8606:"bfd857cd",8690:"a86bc727",8714:"3ec7ab82",9100:"5ce11220",9114:"540bd4bc",9159:"697a8fc3",9267:"db0474c2",9387:"aa3161ce",9427:"319d3a6f",9698:"20a7fbb6"}[e]+".js",b.miniCssF=e=>{},b.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),b.o=(e,d)=>Object.prototype.hasOwnProperty.call(e,d),c={},f="website:",b.l=(e,d,a,t)=>{if(c[e])c[e].push(d);else{var r,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{r.onerror=r.onload=null,clearTimeout(s);var f=c[e];if(delete c[e],r.parentNode&&r.parentNode.removeChild(r),f&&f.forEach((e=>e(a))),d)return d(a)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:r}),12e4);r.onerror=u.bind(null,r.onerror),r.onload=u.bind(null,r.onload),o&&document.head.appendChild(r)}},b.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),b.p="/",b.gca=function(e){return e={17896441:"8401",27315305:"2419",60691868:"8055",cfd71120:"15","283d7b21":"76","7c10977a":"98",d558f29a:"119",a81f2d1d:"488","476d6aec":"513","21418ead":"515","373392d9":"979","14a2b9f8":"1224",eb83943b:"1493","90f022e0":"1577","2dc45ced":"1739",b2b675dd:"1991","5e677a75":"2008",d22a0e6a:"2184",f349a764:"2329",c4f5d8e4:"2634","9e4087bc":"2711",a5dc57e5:"2804",e72a3ded:"2894",f228e963:"2982",cc8d6d7f:"2984",ccc49370:"3249","246b6efd":"4024",b16bf7d2:"4319","391ef999":"4324","3e65d5b4":"4363",af32bd62:"4487","6875c492":"4813","5baf5c08":"4959","9d050fe4":"5016","41206b0e":"5033","581d6198":"5147",b2f554cd:"5894",a09c2993:"5899","473435fa":"6002","31fe93dc":"6119","5c4c46e6":"6307","432e378d":"6533","37fc9d46":"6621","33b5e0ca":"6669",abda9da4:"6835","814f3328":"7472",ca95d5ad:"7483",a6aa9e1f:"7643","0b5ee478":"7733",b0b0b448:"7941","2dfecbce":"8025","01a85c17":"8209",af94d498:"8262",b8b1253e:"8481","935f2afb":"8581","8293a772":"8606","2651e53d":"8690","1be78505":"8714","1a20bc57":"9114",c83e76ae:"9159",a7023ddc:"9267","6b032a97":"9387","31d6d5ed":"9427",ea32bb6f:"9698"}[e]||e,b.p+b.u(e)},(()=>{var e={5354:0,1869:0};b.f.j=(d,a)=>{var c=b.o(e,d)?e[d]:void 0;if(0!==c)if(c)a.push(c[2]);else if(/^(1869|5354)$/.test(d))e[d]=0;else{var f=new Promise(((a,f)=>c=e[d]=[a,f]));a.push(c[2]=f);var t=b.p+b.u(d),r=new Error;b.l(t,(a=>{if(b.o(e,d)&&(0!==(c=e[d])&&(e[d]=void 0),c)){var f=a&&("load"===a.type?"missing":a.type),t=a&&a.target&&a.target.src;r.message="Loading chunk "+d+" failed.\n("+f+": "+t+")",r.name="ChunkLoadError",r.type=f,r.request=t,c[1](r)}}),"chunk-"+d,d)}},b.O.j=d=>0===e[d];var d=(d,a)=>{var c,f,t=a[0],r=a[1],o=a[2],n=0;if(t.some((d=>0!==e[d]))){for(c in r)b.o(r,c)&&(b.m[c]=r[c]);if(o)var i=o(b)}for(d&&d(a);n Archive | Glean - + - + \ No newline at end of file diff --git a/blog/incremental/index.html b/blog/incremental/index.html index 9da350101..ac7d883a5 100644 --- a/blog/incremental/index.html +++ b/blog/incremental/index.html @@ -5,7 +5,7 @@ Incremental indexing with Glean | Glean - + @@ -89,7 +89,7 @@ ownership of x is {A} and y is {B,C} (because it is referred to from z which has owner B), so the final owner of d is {A} && {B,C}.

Tracking all this shouldn't be too expensive, but it's tricky to get right!

- + \ No newline at end of file diff --git a/blog/index.html b/blog/index.html index 94ad4d08f..4036f671c 100644 --- a/blog/index.html +++ b/blog/index.html @@ -5,7 +5,7 @@ Blog | Glean - + @@ -89,7 +89,7 @@ ownership of x is {A} and y is {B,C} (because it is referred to from z which has owner B), so the final owner of d is {A} && {B,C}.

Tracking all this shouldn't be too expensive, but it's tricky to get right!

- + \ No newline at end of file diff --git a/blog/tags/glean/index.html b/blog/tags/glean/index.html index 580206003..30bbca13f 100644 --- a/blog/tags/glean/index.html +++ b/blog/tags/glean/index.html @@ -5,7 +5,7 @@ One post tagged with "glean" | Glean - + @@ -89,7 +89,7 @@ ownership of x is {A} and y is {B,C} (because it is referred to from z which has owner B), so the final owner of d is {A} && {B,C}.

Tracking all this shouldn't be too expensive, but it's tricky to get right!

- + \ No newline at end of file diff --git a/blog/tags/incremental/index.html b/blog/tags/incremental/index.html index 1728fefba..b0e4176d0 100644 --- a/blog/tags/incremental/index.html +++ b/blog/tags/incremental/index.html @@ -5,7 +5,7 @@ One post tagged with "incremental" | Glean - + @@ -89,7 +89,7 @@ ownership of x is {A} and y is {B,C} (because it is referred to from z which has owner B), so the final owner of d is {A} && {B,C}.

Tracking all this shouldn't be too expensive, but it's tricky to get right!

- + \ No newline at end of file diff --git a/blog/tags/index.html b/blog/tags/index.html index 3564da111..a4709babe 100644 --- a/blog/tags/index.html +++ b/blog/tags/index.html @@ -5,14 +5,14 @@ Tags | Glean - + - + \ No newline at end of file diff --git a/docs/angle/advanced/index.html b/docs/angle/advanced/index.html index dd7eda4e4..04a119594 100644 --- a/docs/angle/advanced/index.html +++ b/docs/angle/advanced/index.html @@ -5,14 +5,14 @@ Advanced Query Features | Glean - +

Advanced Query Features

Types and signatures

Angle queries are strongly typed: the server will check your query for type-safety before executing it. Type-checking ensures that the query makes sense; that it's not trying to pattern-match strings against integers, or look for a field in a record that doesn't exist for example.

Angle's type-checker isn't very clever, though. It mostly doesn't do type inference, it checks that expressions have the intended type. When it doesn't know the intended type of an expression, it uses a dumb inference mode that can only infer the type when it's really obvious: like a fact match, or a string.

facts> P where C = { name = "Fish" }; example.Parent { C, P }
can't infer the type of: {name = "Fish"}
try adding a type annotation like ({name = "Fish"} : T)
or reverse the statement (Q = P instead of P = Q)

In cases like this, Angle's type-checker needs a bit of help. We can use a type signature to supply more information about the type:

facts> P where C = { name = "Fish" } : example.Class; example.Parent { C, P }
{ "id": 1024, "key": { "name": "Pet", "line": 10 } }

Here we used { name = "Fish" } : example.Class to tell Angle the expected type of the pattern. You should read the colon as "has type", and the type can be any valid Angle type, for details see Built-in types.

Explicit fact IDs

Every fact has an ID, which is a 64-bit integer that uniquely identifies the fact in a particular database. You've probably noticed these fact IDs in the query results: every result has an id field with the fact ID, and a key field with the fact key.

Most Angle queries don't need to mention fact IDs explicitly, but sometimes it's useful. For example, you might need to perform a query to fetch some results, do some custom filtering on the results and then query Glean again using some of the fact IDs from the first query.

WARNING: a fact ID only makes sense in the context of a particular database, so make sure that your query that mentions fact IDs is being made on the same database that you obtained the fact ID from originally.

Glean has a syntax for referring to fact IDs directly; for example

facts> $1026 : example.Class
{ "id": 1026, "key": { "name": "Fish", "line": 30 } }

the syntax is $<fact ID>, but you will often want to use it with a type signature, as $<fact ID> : <predicate>.

If you get the predicate wrong, Glean will complain:

facts> $1026 : example.Parent
*** Exception: fact has the wrong type

The type can be omitted only if it is clear from the context, for example

facts> example.Parent { child = $1026 }
{ "id": 1029, "key": { "child": { "id": 1026 }, "parent": { "id": 1024 } } }

Sometimes you might want to use multiple fact IDs in a query. Choice (|) comes in handy here:

facts> example.Parent { child = $1026 | $1027 }

Functional predicates

All the predicates we've seen so far have been key-only predicates. A predicate can also have a value; we call these functional predicates or key-value predicates.

For example, we might model a reference to a class in our example schema like this:

predicate Reference :
{ file : string, line : nat, column : nat } -> Class

This says that for a given (file,line,column) there can be at most one reference to a Class. This uniqueness is the important property of a key-value predicate: for each key there is at most one value.

We query for key-value predicates using this syntax:

facts> C where example.Reference { file = "x", line = 1, column = 2 } -> C

The pattern after the -> matches the value. It can be an arbitrary pattern, just like the key. Note that facts cannot be efficiently searched by value, so the pattern that matches the value is a filter only.

- + \ No newline at end of file diff --git a/docs/angle/debugging/index.html b/docs/angle/debugging/index.html index de0f282b1..9d0ef2944 100644 --- a/docs/angle/debugging/index.html +++ b/docs/angle/debugging/index.html @@ -5,7 +5,7 @@ Debugging | Glean - + @@ -15,7 +15,7 @@ shell, where you can experiment with queries quickly and easily.

If you're writing particularly complex queries, then consider using Derived Predicates to structure your query and to allow parts of the query to be re-used. To iterate on derived predicates, see How do I write and test a derived predicate?

Debugging a slow query

Performance debugging can be tricky, because Angle is a very declarative language. There are often many ways to write the query that are correct, but not all of them will be fast.

The shell provides a few facilities to help with this.

> :profile full

Turning on query profiling allows you to see how many facts of each predicate are being searched by your query. For example:

fbsource> codemarkup.search.SearchByName { name = "Future", searchcase = Sensitive }
...
Facts searched:
cxx1.DeclarationNameSpan.5 : 100
cxx1.RecordDeclarationClass.5 : 78
cxx1.RecordDefinition.5 : 19
cxx1.NamespaceDeclarationByName.5 : 13
cxx1.NamespaceDefinition.5 : 13

If your query is expensive, then likely you will see some large numbers next to one or more predicates. This is a sign that you probably want to reorder the statements in your query, or lift out some nested queries into statements so that you can control the ordering more precisely.

Showing the internals

The shell provides ways to show what Glean's query engine is doing internally. This is mostly useful for those working on the query engine itself, but it might also be helpful when debugging queries.

danger

We provide no guarantees about this functionality and it might change without warning.

> :debug ir

Shows the internal representation of the query after parsing, name resolution, type checking, and various transformations to simplify it. In particular, all the nesting has been flattened at this stage, so you can see the exact order of the searches on each predicate, which might help with performance debugging.

> :debug bytecode

Shows the compiled bytecode for the query. This is what Glean's virtual machine (VM) will execute to perform the query. Probably not all that useful for debugging queries.

- + \ No newline at end of file diff --git a/docs/angle/efficiency/index.html b/docs/angle/efficiency/index.html index 4cf22c77a..4b8089945 100644 --- a/docs/angle/efficiency/index.html +++ b/docs/angle/efficiency/index.html @@ -5,14 +5,14 @@ Query Efficiency | Glean - +

Query Efficiency

There are two important aspects of a query that affect its efficiency;

  1. Which fields are specified in a pattern
  2. The ordering of statements

We’ll cover each of these in the following sections.

Efficient matching of facts

The order of fields in the schema matters a lot for efficiency. Glean indexes facts by a prefix of their keys, so if we know the prefix when searching for facts this will be a lot faster. Often this difference is absolutely crucial; the difference is between O(log n) and O(n), so when the database is large this can be many orders of magnitude.

For example, the example.Parent predicate we saw earlier is defined as

predicate Parent :
{
child : Person,
parent : Person,
}

We should think of this as a mapping from child to parent. Glean won’t stop you writing a query for { parent = ... }, but such a query will examine all of the example.Parent facts in the database. We can see how many facts are searched for our query using :profile full in the shell (see debugging for more details):

facts> :profile full
facts> example.Parent { parent = { name = "Pet" }}
(snip)
2 results, 2 facts, 0.40ms, 159440 bytes, 988 compiled bytes
Facts searched:
example.Parent.1 : 3

This tells us that although it found the 2 results we expected, it searched all 3 example.Parent facts in the process.

Making queries efficient using a derived predicate

What if we wanted to efficiently map from parent to child? That’s easy to accomplish using a derived predicate. We’re going to define a new predicate with a different field ordering, and automatically generate the facts of our new predicate by deriving them from the facts of the existing predicate. For full details see Derived Predicates, what follows will be a walkthrough showing how to use a derived predicate to make our queries more efficient.

First we’ll define our derived predicate in the schema, like this:

predicate Child :
{
parent : Class,
child : Class,
}
stored
{ P, C } where Parent { C, P }

We can try this out in the shell. First we have to create a new database to hold the derived facts that is stacked on top of the old database. Drop out of the shell and run this command to create the new database:

glean create --db-root /tmp/glean/db --schema dir:/tmp/glean/schema --db derived/1 --stacked facts/1

Now start the shell again and load the stacked database. Note that we can still query facts from the original database:

> :db derived/1
derived> example.Parent _
{ "id": 1028, "key": { "child": { "id": 1025 }, "parent": { "id": 1024 } } }
{ "id": 1029, "key": { "child": { "id": 1026 }, "parent": { "id": 1024 } } }
{ "id": 1030, "key": { "child": { "id": 1027 }, "parent": { "id": 1026 } } }

Initially we have no facts of the Child predicate:

derived> example.Child _
0 results, 0 facts, 0.91ms, 812952 bytes, 664 compiled bytes

But we can create them automatically:

derived> * example.Child _
{ "id": 1037, "key": { "parent": { "id": 1024 }, "child": { "id": 1025 } } }
{ "id": 1038, "key": { "parent": { "id": 1024 }, "child": { "id": 1026 } } }
{ "id": 1039, "key": { "parent": { "id": 1026 }, "child": { "id": 1027 } } }

(the * means “derive and store” the facts produced by the query. To derive facts for a production database you would use either glean derive from the command line, or the appropriate Thrift API in whatever language you’re using to talk to the Glean server).

Now we have 3 facts of our derived predicate:

derived> :stat
example.Child.1
count: 3
size: 87 (87 bytes) 100.0000%

And finally we can make efficient queries to find a parent’s children:

derived> example.Child { parent = { name = "Pet" }}
{ "id": 1037, "key": { "parent": { "id": 1024 }, "child": { "id": 1025 } } }
{ "id": 1038, "key": { "parent": { "id": 1024 }, "child": { "id": 1026 } } }

2 results, 2 facts, 0.41ms, 160992 bytes, 1013 compiled bytes
Facts searched:
example.Child.1 : 2
example.Class.1 : 1

We found the correct 2 results, and only searched 2 example.Child facts.

This idea of adding extra indices to your database using derived predicates is common practice when working with Glean data, so it’s worthwhile getting familiar with it.

The order of statements is important

Suppose we want to find the grandparent of the Goldfish class using our example schema. We would probably write it like this:

Q where
example.Parent { child = { name = "Goldfish" }, parent = P };
example.Parent { child = P, parent = Q }

Generally speaking the statements are matched top-to-bottom. For each of the facts that match the first statement, bind the variables in the pattern and then proceed with the second statement, and so on.

As written, this query works by first finding the parent of Goldfish and then finding its parent, which is exactly what we want. This query will be efficient, because both stages are matching on the first field of the example.Parent predicate.

If instead we swapped the order of the statements:

Q where
example.Parent { child = P, parent = Q };
example.Parent { child = { name = "Goldfish" }, parent = P }

The query still works, and means exactly the same thing, but it’s much less efficient. This query works as follows:

  • for each example.Parent fact, call the child P and the parent Q
  • search for an example.Parent fact with child { name = "Goldfish" } and parent P
  • if it exists, then Q is a result

This is going to involve searching all of the example.Parent facts, instead of just the ones for the parent of Goldfish.

The general rule of thumb is to do the more specific searches first. The search for example.Parent { child = { name = "Goldfish" }, parent = P } is efficient because we know the child, this binds he value of P which makes the search for example.Parent { child = P, parent = Q } also fast.


- + \ No newline at end of file diff --git a/docs/angle/guide/index.html b/docs/angle/guide/index.html index 79814b2bd..ace35f855 100644 --- a/docs/angle/guide/index.html +++ b/docs/angle/guide/index.html @@ -5,7 +5,7 @@ Angle Guide | Glean - + @@ -45,7 +45,12 @@ never be evaluated if the condition succeeds at least once.

For example, we could get all child classes if inheritance is being used in the codebase, or retrieve all classes if it isn't.

facts > if (example.Parent { child = X }) then X else example.Class _
{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }
{ "id": 1026, "key": { "name": "Fish", "line": 30 } }
{ "id": 1027, "key": { "name": "Goldfish", "line": 40 } }

Please note that if cannot be used in stored derived predicates. This is the case because they require the use of negation, which is disallowed in -stored predicates.

Arrays

When the schema uses an array, we need to be able to write queries that traverse the elements of the array. For example, a common use of an array is to represent the list of declarations in a source file. Our example schema defines the FileClasses predicate:

predicate FileClasses :
{
file : string,
classes : [Class]
}

The goal here is to map efficiently from a filename to the list of classes defined in that file. Suppose we want to write a query that finds all the classes called Goldfish in the file petshop.example, we could do it like this:

example.FileClasses { file = "petshop.example", classes = Cs };
{ name = "Goldfish" } = Cs[..]

The second line is the interesting one: { name = "Goldfish" } = Cs[..] means

  • on the right-hand side, Cs[..] means “each element of the array Cs
  • the left-hand side is a pattern, filtering only those Class facts that match { name = "Goldfish" }

We can also match the whole array with a pattern of the form [ p1, p2, .. ]

facts> X where [_,X,_] = [1,2,3]
{ "id": 1040, "key": 2 }

Or if we don't care about the length of the array:

facts> X where [_,X, ..] = [1,2,3]
{ "id": 1040, "key": 2 }

String prefix

We’ve seen many examples of patterns that match strings. Glean also supports matching strings by prefix; for example:

facts> example.Class { name = "F".. }
{ "id": 1026, "key": { "name": "Fish", "line": 30 } }

The syntax "F".. means strings beginning with the prefix ”F".

note

Why only prefix and not substring matching in general? Prefix matching can be supported efficiently by Glean’s prefix-tree representation of the fact database. Other kinds of string matching could be supported, but they wouldn’t be able to exploit the database representation so there’s little advantage to implementing them in Angle compared with filtering on the client-side.

Tuples

A tuple is just a a way of writing a record without the field names. So for example, instead of

example.Parent { child = C }

we could write

example.Parent { C, _ }

When using a tuple you have to list all the fields, in the same order as they are declared in the schema. That's why { child = C } becomes { C, _ } when written as a tuple.

There are upsides and downsides to using the tuple notation:

  • Pro: more concise
  • Con: brittle and sensitive to changes in the schema. If we add a field, then tuple patterns will break whereas record patterns won't.

As a rule of thumb we tend to use tuple syntax in cases where the predicate is "obviously" a relation, such as example.Parent, but we wouldn't use tuple syntax for more complex records.

Enums and bool

An enum type is a set of named constants. In the Has predicate we used an enum type to indicate whether a class member is public_ or private_:

predicate Has :
{
class_ : Class,
has : Member,
access : enum { public_ | private_ },
}

To match an enum we just use the appropriate identifier, in this case public_ or private:

facts> example.Has { access = private_ }
{ "id": 1036, "key": { "class_": { "id": 1026 }, "has": { "id": 1035 }, "access": 1 } }

Note that in the JSON format results, an enum is represented by an integer. When you make queries in code, the enum will be represented by an appropriate type, such as a data type in Haskell.

The boolean type bool is a special case of an enum, defined like this:

type bool = enum { false | true }
note

Normally the constants of an enum should begin with a lower case +stored predicates.

Arrays

When the schema uses an array, we need to be able to write queries that traverse the elements of the array. For example, a common use of an array is to represent the list of declarations in a source file. Our example schema defines the FileClasses predicate:

predicate FileClasses :
{
file : string,
classes : [Class]
}

The goal here is to map efficiently from a filename to the list of classes defined in that file. Suppose we want to write a query that finds all the classes called Goldfish in the file petshop.example, we could do it like this:

example.FileClasses { file = "petshop.example", classes = Cs };
{ name = "Goldfish" } = Cs[..]

The second line is the interesting one: { name = "Goldfish" } = Cs[..] means

  • on the right-hand side, Cs[..] means “each element of the array Cs
  • the left-hand side is a pattern, filtering only those Class facts that match { name = "Goldfish" }

We can also match the whole array with a pattern of the form [ p1, p2, .. ]

facts> X where [_,X,_] = [1,2,3]
{ "id": 1040, "key": 2 }

Or if we don't care about the length of the array:

facts> X where [_,X, ..] = [1,2,3]
{ "id": 1040, "key": 2 }

Sets

Sets are similar to arrays but helpful when the order of the elements are not important and duplicates are also irrelevant. +A common example is when storing cross references. For instance, the python schema has a predicate which contains all +name cross references in a file. The cross references are currently stored in an array but it could be stored in a set as below.

predicate XRefsViaNameByFile:
{
file: src.File,
xrefs: set XRefViaName,
}

If we want to know for a particular file and a particular name, where it is used we could write the following query:

XRefsViaNameByFile { file = "foo.py", xrefs = XRefs };
{ target = { name = "Bar" } } = elements XRefs

The second line uses the construct elements which is similar to the [..] syntax for arrays.

We can also create new sets from the results of a query. This is done using the all construct. For instance +all (1 | 2 | 3) is a set containing the number 1, 2, and 3.

The all construct can be used in combination with the elements construct to, for instance, map over a set +of elements and transform them. In the example below, the second line takes each element of the StringSet and +applies the primitive prim.toLower to it. The result is a set where all the strings are lowercase.

StringSet = all ("Foo" | "Bar" | "Baz" );
all (String = elements StringSet; prim.toLower String)

String prefix

We’ve seen many examples of patterns that match strings. Glean also supports matching strings by prefix; for example:

facts> example.Class { name = "F".. }
{ "id": 1026, "key": { "name": "Fish", "line": 30 } }

The syntax "F".. means strings beginning with the prefix ”F".

note

Why only prefix and not substring matching in general? Prefix matching can be supported efficiently by Glean’s prefix-tree representation of the fact database. Other kinds of string matching could be supported, but they wouldn’t be able to exploit the database representation so there’s little advantage to implementing them in Angle compared with filtering on the client-side.

Tuples

A tuple is just a a way of writing a record without the field names. So for example, instead of

example.Parent { child = C }

we could write

example.Parent { C, _ }

When using a tuple you have to list all the fields, in the same order as they are declared in the schema. That's why { child = C } becomes { C, _ } when written as a tuple.

There are upsides and downsides to using the tuple notation:

  • Pro: more concise
  • Con: brittle and sensitive to changes in the schema. If we add a field, then tuple patterns will break whereas record patterns won't.

As a rule of thumb we tend to use tuple syntax in cases where the predicate is "obviously" a relation, such as example.Parent, but we wouldn't use tuple syntax for more complex records.

Enums and bool

An enum type is a set of named constants. In the Has predicate we used an enum type to indicate whether a class member is public_ or private_:

predicate Has :
{
class_ : Class,
has : Member,
access : enum { public_ | private_ },
}

To match an enum we just use the appropriate identifier, in this case public_ or private:

facts> example.Has { access = private_ }
{ "id": 1036, "key": { "class_": { "id": 1026 }, "has": { "id": 1035 }, "access": 1 } }

Note that in the JSON format results, an enum is represented by an integer. When you make queries in code, the enum will be represented by an appropriate type, such as a data type in Haskell.

The boolean type bool is a special case of an enum, defined like this:

type bool = enum { false | true }
note

Normally the constants of an enum should begin with a lower case letter. You can use constants beginning with an upper-case letter, but to distinguish the constant from a variable name you may sometimes need to provide a type signature, e.g. Constant : Type rather than @@ -54,8 +59,8 @@ result.

For example, we can find classes that don't have methods

facts> C where C = example.Class _; !(example.Has { class_ = C, has = { method = _ } })
{ "id": 1026, "key": { "name": "Fish", "line": 30 } }
{ "id": 1027, "key": { "name": "Goldfish", "line": 40 } }
{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }

Or we could find the maximum element in an array

facts> X where Values = [5,1,2,3]; X = Values[..]; !(Y = Values[..]; Y > X)
{ "id": 1091, "key": 5 }

The query asks for the X for which given all values of Y none is greater than it. If Y = Values[..] were outside of the negation, the meaning would be give me all X for which there is at least one Y that is not greater -than it. The answer to that would be all elements.

- +than it. The answer to that would be all elements.

+ \ No newline at end of file diff --git a/docs/angle/intro/index.html b/docs/angle/intro/index.html index cb91da4c8..f7a1931c8 100644 --- a/docs/angle/intro/index.html +++ b/docs/angle/intro/index.html @@ -5,7 +5,7 @@ Angle Introduction | Glean - + @@ -16,7 +16,7 @@ particularly suited for finding and extracting data from Glean.

To give you a flavour of the query language, here is how we could return the names of all the member declarations defined in a JavaScript file project/myfile.js:

D.declaration.memberDecl?.name
where
D : flow.FileDeclaration;
D.file = src.File "project/myfile.js"

To learn about Angle, start with the Guide.

- + \ No newline at end of file diff --git a/docs/angle/reference/index.html b/docs/angle/reference/index.html index 91de6b758..3e74dfecf 100644 --- a/docs/angle/reference/index.html +++ b/docs/angle/reference/index.html @@ -5,7 +5,7 @@ Angle Reference | Glean - + @@ -17,14 +17,15 @@ ...
for each value of statementₙ
produce the value of term

If term where is omitted, then the query produces the values of the final statement. For example, a query src.File "foo/bar" is equivalent to F where F = src.File "foo/bar".

Note that a query corresponds to a nested loop, where statement₀ is the outermost loop, and statementₙ is the innermost. The ordering of the statements can therefore have a significant effect on performance.

Statements

statement ::= [ term₁ = ] term₂

match all values of term₁ against all values of term₂

The order is mostly irrelevant; A = B is equivalent to B = A, except that type inference works by inferring the right-hand-side before checking the left-hand-side so this may influence which order you want. You can also use a type signature (A = B : type) to help the type checker.

Names

Glean uses the following classes of names:

  • A schema name, e.g. example.schema, of the form name[.name]*. By convention, the components of a schema name begin with a lower-case letter.
  • A predicate name, e.g. example.schema.Predicate.1 of the form schema.predicate[.version]. By convention, predicate begins with an upper-case letter. The version can often be omitted, in which case it defaults depending on the context: in a query it defaults to the most recent version, in a schema there is always only one version of a predicate visible in any given scope.
  • A field name, e.g. declaration, used to identify fields of a record, or alternatives of a sum type or enumeration. A field name must begin with a lower-case letter.
  • A variable, e.g. X. Variables must begin with an upper-case letter to distinguish them from field names.

There is a set of reserved words that can't be used for names. Mostly this is because those words would clash with reserved keywords in code that we generate from the schema, and we don't want to have to do any automatic translation of names that might be confusing. Typically the convention for avoiding these reserved words is to add an underscore to the name, e.g. class_.

Term

A term may be fully defined, like { true, 123 } (a value that we could insert in the database), or it can be partially defined, like { A, "b", _ }.

A term is often matched against something that will instantiate its unknown variables. For example, in cxx.Name X, we're instantitating the variable X to each of the keys of the predicate cxx.Name.

Ultimately the result of a query must be terms that are fully defined, though. If this isn't the case, Glean's query engine will report an error. For example, a query like X where 123 doesn't make sense, because we haven't matched X with anything.

Terms have the following forms:

term ::=
-   variable

A variable names the terms that match at this position in the query. The variable can be mentioned elsewhere in the query; it doesn't usually make sense for a variable to be mentioned only once, since then you might as well just use a wildcard, see below.

   _

A wildcard; matches anything

   never

A pattern that always fails to match.

  predicate term [ -> term ]

All the facts of predicate with keys that match the first term (and values that match the second term if appropriate)

  ( query )

All the values of query. Note in particular that query can just be a simple term, but it can also be something like term where statements.

  term [..]

All the elements of the array term

  term₁ | term₂

When used as a pattern, matches term₁ or term₂. When used as an expression, generates all values of term₁ and all values of term₂.

Note: variables mentioned in term₁ and term₂ are local to those terms, and may have different types, but only if the variable is not mentioned elsewhere.

  ! term

The negation of a term. Fails if the term matches anything and succeeds otherwise.

  if term₁ then term₂ else term₃

If term₁ has matches the then branch is taken and term₃ is never matched against. If term₁ has no matches then the else branch is taken and term₂ is never matched against.

   [0-9]+

a number matches a value of type nat or byte

   string

a string matches a value of type string

   string ..

matches strings with the given prefix

   string .. term

matches a prefix and the rest of the string

   { field = term , ... }

matches a record with the given fields

   { field = term }

matches a sum type with an alternative field

   field

when matching a sum type, shorthand for { field = _ }

   enumerator

matches an value of an enumerated type

   { just = term }
+   variable

A variable names the terms that match at this position in the query. The variable can be mentioned elsewhere in the query; it doesn't usually make sense for a variable to be mentioned only once, since then you might as well just use a wildcard, see below.

   _

A wildcard; matches anything

   never

A pattern that always fails to match.

  predicate term [ -> term ]

All the facts of predicate with keys that match the first term (and values that match the second term if appropriate)

  ( query )

All the values of query. Note in particular that query can just be a simple term, but it can also be something like term where statements.

  term [..]

All the elements of the array term

  term₁ | term₂

When used as a pattern, matches term₁ or term₂. When used as an expression, generates all values of term₁ and all values of term₂.

Note: variables mentioned in term₁ and term₂ are local to those terms, and may have different types, but only if the variable is not mentioned elsewhere.

  elements term

All the elements of the set term

  all query

Construct a set of all the results of query.

  ! term

The negation of a term. Fails if the term matches anything and succeeds otherwise.

  if term₁ then term₂ else term₃

If term₁ has matches the then branch is taken and term₃ is never matched against. If term₁ has no matches then the else branch is taken and term₂ is never matched against.

   [0-9]+

a number matches a value of type nat or byte

   string

a string matches a value of type string

   string ..

matches strings with the given prefix

   string .. term

matches a prefix and the rest of the string

   { field = term , ... }

matches a record with the given fields

   { field = term }

matches a sum type with an alternative field

   field

when matching a sum type, shorthand for { field = _ }

   enumerator

matches an value of an enumerated type

   { just = term }
   nothing

matches a maybe type

   true
   false

matches a boolean

   term : type

(a type signature) interpret term as having type type, where type is any valid Angle type.

   $ [0-9]+

matches a literal fact ID. The only reason to use these would be if you did a previous query, extracted some fact IDs, and want to do a subsequent query incorporating them. Literal fact IDs are not allowed in derived predicates (it wouldn't make any sense).

Primitives

Angle supports a few primitive operations. The argument(s) to a primitive operation must always be fully defined; they cannot be patterns or wildcards.

  prim.toLower (S : string) : string

Converts its string argument to lower case

  prim.length (A : [_]) : nat

Equal to the number of elements in its array argument

  term > term
  term >= term
  term < term
  term <= term
-  term !== term

Standard numerical comparisons. These work on values of type nat only, and they have value {} if the comparison succeeds, otherwise they fail (in the same way as a predicate match fails if there are no facts that match the pattern).

  term != term

Standard comparison between two terms of any type. It has a value of {} if the comparison succeeds, otherwise it fails in the same way as a predicate match fails if there are no facts that match the pattern.

- +  term !== term

Standard numerical comparisons. These work on values of type nat only, and they have value {} if the comparison succeeds, otherwise they fail (in the same way as a predicate match fails if there are no facts that match the pattern).

  term != term

Standard comparison between two terms of any type. It has a value of {} if the comparison succeeds, otherwise it fails in the same way as a predicate match fails if there are no facts that match the pattern.

  zip (A : [a]) (B : [b]) : [{a,b}]

Takes two arrays and zips them together pairwise into a new array of tuples. +If the arrays have different length, the result has the same length as the shorter input array.

  concat (A : [a]) (B : [a]) : [a]

Concatenates two arrays together

  reverse (S : string) : string

Reverses a string

+ \ No newline at end of file diff --git a/docs/angle/style/index.html b/docs/angle/style/index.html index 2569dcddf..f980e8128 100644 --- a/docs/angle/style/index.html +++ b/docs/angle/style/index.html @@ -5,14 +5,14 @@ Angle Style Guide | Glean - +

Angle Style Guide

Typical Angle style uses the following rules:

  • 2-column indentation
  • trailing commas
  • open/close braces on a line by themselves
  • camel case for record field names

e.g.

# Named parameter
type Parameter =
{
name : Name,
type : Type,
isVariadic : bool,
}

This uses quite a lot of vertical space, but it's clear and works well with source control.

It's OK to put things on a single line if they fit:

type Access = enum { public | protected | private }
- + \ No newline at end of file diff --git a/docs/building/index.html b/docs/building/index.html index 3c90beda2..b276c7564 100644 --- a/docs/building/index.html +++ b/docs/building/index.html @@ -5,7 +5,7 @@ Building Glean from Source | Glean - + @@ -26,7 +26,7 @@ build and install its dependencies:

./install_deps.sh

Build Glean

Now you can build all the Glean parts:

make

If everything worked, the tests should pass:

make test

At this point you can cabal install to install the executables into ~/.cabal/bin.

Tips for faster builds

If you have 4 or more cores and at least 16G of ram, you can significantly speed up the build times by passing some flags to the build stages. On an 6 core machine with 16G of ram you might use, to save 50% or more of the build time.

./install_deps.sh --threads 6
make EXTRA_GHC_OPTS='-j4 +RTS -A128m -n2m -RTS'

Using clang++-12 and clang-12 as the C and C++ compilers can shave another 25% off the build time.

- + \ No newline at end of file diff --git a/docs/cli/index.html b/docs/cli/index.html index 4f5f36d91..439668e87 100644 --- a/docs/cli/index.html +++ b/docs/cli/index.html @@ -5,7 +5,7 @@ The Glean CLI tool | Glean - + @@ -73,7 +73,7 @@ once a database is marked complete it could be replicated, so we shouldn't be modifying it.

  • --db NAME/INSTANCE or --db-name NAME --db-instance INSTANCE
    Specifies the name and instance of the database
- + \ No newline at end of file diff --git a/docs/databases/index.html b/docs/databases/index.html index 73baeba13..68056fde1 100644 --- a/docs/databases/index.html +++ b/docs/databases/index.html @@ -5,7 +5,7 @@ Glean Databases | Glean - + @@ -20,7 +20,7 @@ index the current state of a source repository. The process works like this:

  • The job invokes glean create --service <write-server> <args> to create the database.

  • At this point the database is in the Incomplete state. Queries are supported in this state, and always reflect the current contents.

  • Facts are written to the database using the methods described in Writing data to Glean, and finally the database is closed by invoking glean finish --service <write-server> <args> or the appropriate Thrift method.

  • The database is now in the Complete state.

  • If backups are allowed for this database, then:

    • the write server uploads the database to backup storage.
    • servers that are configured to restore databases automatically can download the DB from backup storage, and use it to serve queries from clients.
note

There are currently no backup backends implemented for open-source Glean.

- + \ No newline at end of file diff --git a/docs/derived/index.html b/docs/derived/index.html index d37341ee8..83a3bdd4f 100644 --- a/docs/derived/index.html +++ b/docs/derived/index.html @@ -5,7 +5,7 @@ Derived Predicates | Glean - + @@ -39,7 +39,7 @@ describes the data in the rest of the stack.

If you need to test changes to an existing predicate, copy the predicate and give it a new name to test it, and then fold the changes back into the original when you've finished testing.

Now, you can derive your new predicate:

glean derive --db-root ~/local/gleandb --db stacked/0 my.new.Predicate

and inspect the results in the shell:

glean shell --db-root ~/local/gleandb --db stacked/0
- + \ No newline at end of file diff --git a/docs/implementation/incrementality/index.html b/docs/implementation/incrementality/index.html index 9c727119f..d0919eb4f 100644 --- a/docs/implementation/incrementality/index.html +++ b/docs/implementation/incrementality/index.html @@ -5,7 +5,7 @@ Incrementality | Glean - + @@ -97,7 +97,7 @@ ownership of the derived facts. Incremental derivation must therefore consider facts that have new ownership in the stacked DB when deriving. At the time of writing, this isn't implemented yet.

- + \ No newline at end of file diff --git a/docs/indexer/cxx/index.html b/docs/indexer/cxx/index.html index 5a9b862cb..dc585ad65 100644 --- a/docs/indexer/cxx/index.html +++ b/docs/indexer/cxx/index.html @@ -5,7 +5,7 @@ C++ and C | Glean - + @@ -28,7 +28,7 @@ PATH variable for this to succeed, or in the build tree.

Schema

The schema is in glean/schema/source/cxx.angle

The schema is quite rich and captures C++, C, Objective-C and C pre-processor symbols, the semantic structure of C++ symbols, and is precise enough to do automated analysis of C++ code.

- + \ No newline at end of file diff --git a/docs/indexer/flow/index.html b/docs/indexer/flow/index.html index 7f74da7ca..b351f0273 100644 --- a/docs/indexer/flow/index.html +++ b/docs/indexer/flow/index.html @@ -5,7 +5,7 @@ JavaScript (Flow) | Glean - + @@ -16,7 +16,7 @@ in the Glean demo Docker image to try out.

Run the indexer

The indexer is run via the main glean CLI tool.

> cabal build exe:glean

And index your Flow repository with:

glean index flow DIR --db NAME/INSTANCE

where

  • DIR is the root directory containing the Flow project (with .flowconfig)
  • name/hash is the name of the repository to create

Provide the usual --db-root and --schema or --service arguments to glean

Run the indexer (manually)

flow glean DIR --output-dir JSON --write-root PREFIX

where

  • DIR is the root directory containing the JavaScript/Flow files
  • JSON is the directory in which to write the output .json files
  • PREFIX is a prefix to add to the files in the Glean index (this can be empty if you don't need a prefix)

The generated files can be ingested into a Glean database using glean create.

Derived predicates

Several predicates should be derived after indexing. For each stored predicate in the schema you should glean derive the predicate.

In the shell

Flow source can also be indexed directly from the Glean shell:

:index flow DIR

Schema

The schema is in glean/schema/source/flow.angle

- + \ No newline at end of file diff --git a/docs/indexer/hack/index.html b/docs/indexer/hack/index.html index 5fd0fa5d9..48a68a86a 100644 --- a/docs/indexer/hack/index.html +++ b/docs/indexer/hack/index.html @@ -5,7 +5,7 @@ Hack | Glean - + @@ -13,7 +13,7 @@

Hack

The Hack indexer is built into the Hack typechecker. Stable and nightly binaries of the Hack indexer are available.

Run the indexer

The indexer is run via the main glean CLI tool.

> cabal build exe:glean

And index your Hack repository with:

glean index hack DIR --db NAME/INSTANCE

where

  • DIR is the root directory containing the Hack project (with .hhconfig)
  • name/hash is the name of the repository to create

Provide the usual --db-root and --schema or --service arguments to glean

In the shell

Hack source can also be indexed directly from the Glean shell:

:index hack DIR

Run the indexer (manually)

hh_server DIR --write-symbol-info JSON \
--config symbol_write_include_hhi=false \
--config symbolindex_search_provider=NoIndex \
--config lazy_decl=true \
--config lazy_parse=true \
--config lazy_init2=true \

where

  • DIR is the root directory containing the .php files
  • JSON is the directory in which to write the output .json files
  • We need several config flags to instantiate hh_server for indexing

The generated files can be ingested into a Glean database using glean create.

Derived predicates

Several predicates should be derived after indexing. For each stored predicate in the schema you should glean derive the predicate.

Schema

The schema is in glean/schema/source/hack.angle

- + \ No newline at end of file diff --git a/docs/indexer/haskell/index.html b/docs/indexer/haskell/index.html index 2d44f4767..cde736912 100644 --- a/docs/indexer/haskell/index.html +++ b/docs/indexer/haskell/index.html @@ -5,14 +5,14 @@ Haskell | Glean - +

Haskell

To index Haskell Glean consumes .hie files produced by the GHC compiler (>=8.8) with the flag -fwrite-ide-info.

Run the indexer

The indexer is run via the main glean CLI tool.

BUILD --ghc-options=-fwrite-ide-info
glean --db-root DBDIR index haskell ROOT --db NAME/INSTANCE

where

  • BUILD is a build command such that GHC is called with -fwrite-ide-info
  • DBDIR is the directory where the Glean db will be created
  • ROOT is the root directory containing the build artifacts generated with the fwrite-ide-info flag (e.g. dist if a Cabal project)
  • name/hash is the name of the repository to create

Schema

The schema is in

- + \ No newline at end of file diff --git a/docs/indexer/intro/index.html b/docs/indexer/intro/index.html index 29c3be87b..2a35bb581 100644 --- a/docs/indexer/intro/index.html +++ b/docs/indexer/intro/index.html @@ -5,7 +5,7 @@ Introduction | Glean - + @@ -15,7 +15,7 @@ Glean, and how to use them. Indexers are programs that analyze source code to produce facts for Glean to store. They may be standalone programs, or part of existing IDE or language tools.

- + \ No newline at end of file diff --git a/docs/indexer/lsif-go/index.html b/docs/indexer/lsif-go/index.html index a7f3021b8..fb720416b 100644 --- a/docs/indexer/lsif-go/index.html +++ b/docs/indexer/lsif-go/index.html @@ -5,7 +5,7 @@ Go | Glean - + @@ -13,7 +13,7 @@

Go

To index Go we use SourceGraph's LSIF indexer for Go. LSIF is a new format for tools to share information about code. Binary releases of lsif-go are available ffor x86 Linux which will work as Glean indexers. The LSIF indexer uses a recent (>1.15) version of Go.

Run the indexer

The indexer is run via the main glean CLI tool.

> cabal build exe:glean

And index your Go repository with:

glean index go DIR --db NAME/INSTANCE

where

  • DIR is the root directory containing the Go project
  • name/hash is the name of the repository to create

Provide the usual --db-root and --schema or --service arguments to glean

In the shell

Go source can also be indexed directly from the Glean shell:

:index go DIR

The shell will pick a DB name and hash for you based on DIR.

Schema

The schema is in glean/schema/source/lsif.angle

- + \ No newline at end of file diff --git a/docs/indexer/lsif-java/index.html b/docs/indexer/lsif-java/index.html index 005118f80..77524094c 100644 --- a/docs/indexer/lsif-java/index.html +++ b/docs/indexer/lsif-java/index.html @@ -5,7 +5,7 @@ Java | Glean - + @@ -22,7 +22,7 @@ to glean

In the shell

Java source can also be indexed directly from the Glean shell:

:index java-lsif DIR

The shell will pick a DB name and hash for you based on DIR. You can also run lsif-java offline, and then :load the resulting lsif file into the shell.

Schema

The schema is in glean/schema/source/lsif.angle

- + \ No newline at end of file diff --git a/docs/indexer/lsif-rust/index.html b/docs/indexer/lsif-rust/index.html index 65a0ecc89..b8c2f73c9 100644 --- a/docs/indexer/lsif-rust/index.html +++ b/docs/indexer/lsif-rust/index.html @@ -5,7 +5,7 @@ Rust | Glean - + @@ -13,7 +13,7 @@

Rust

To index Rust we use rust-analyzer in LSIF mode. Pre-built binaries of rust-analyzer can be used as indexers that emit LSIF from Rust source.

Run the indexer

The indexer is run via the main glean CLI tool.

> cabal build exe:glean

And index your Rust repository with:

glean index rust-lsif DIR --db NAME/INSTANCE

where

  • DIR is the root directory containing the Rust project
  • name/hash is the name of the repository to create

Provide the usual --db-root and --schema or --service arguments to glean

In the shell

Rust source can also be indexed directly from the Glean shell:

:index rust-lsif DIR

The shell will pick a DB name and hash for you based on DIR.

Schema

The schema is in glean/schema/source/lsif.angle

- + \ No newline at end of file diff --git a/docs/indexer/lsif-typescript/index.html b/docs/indexer/lsif-typescript/index.html index fff13388d..f1562d489 100644 --- a/docs/indexer/lsif-typescript/index.html +++ b/docs/indexer/lsif-typescript/index.html @@ -5,7 +5,7 @@ TypeScript | Glean - + @@ -13,7 +13,7 @@

TypeScript

To index TypeScript we use SourceGraph's LSIF indexer for TypeScript. LSIF is a new format for tools to share information about code. Releases of lsif-tsc can be installed with yarn or npm and used as indexers for LSIF, which Glean will accept. The indexer itself requires a node.js runtime.

Run the indexer

The indexer is run via the main glean CLI tool.

> cabal build exe:glean

And index your TypeScript repository with:

glean index typescript DIR --db NAME/INSTANCE

where

  • DIR is the root directory containing the TypeScript project
  • name/hash is the name of the repository to create

Provide the usual --db-root and --schema or --service arguments to glean

To index very large TypeScript repositories, it may be necessary to use more heap memory in node.js (or break up the targets into subdirectories). Setting export NODE_OPTIONS="--max-old-space-size=8192" in the environment in which the indexer runs may help.

In the shell

TypeScript source can also be indexed directly from the Glean shell:

:index typescript DIR

The shell will pick a DB name and hash for you based on DIR.

Schema

The schema is in glean/schema/source/lsif.angle

- + \ No newline at end of file diff --git a/docs/indexer/scip-dotnet/index.html b/docs/indexer/scip-dotnet/index.html index 32203ed6a..924742d7e 100644 --- a/docs/indexer/scip-dotnet/index.html +++ b/docs/indexer/scip-dotnet/index.html @@ -5,7 +5,7 @@ Dotnet | Glean - + @@ -13,7 +13,7 @@

Dotnet

To index Dotnet we use SourceGraph's SCIP indexer for dotnet. SCIP is a new format for tools to share information about code. Releases of scip-dotnet can be installed with dotnet tools and used as indexers for SCIP, which Glean will accept. The indexer itself requires a dotnet runtime environment.

Run the indexer

The indexer is run via the main glean CLI tool.

> cabal build exe:glean

And index your Dotnet repository with:

glean index dotnet-scip DIR --db NAME/INSTANCE

where

  • DIR is the root directory containing the Dotnet project
  • name/hash is the name of the repository to create

Provide the usual --db-root and --schema or --service arguments to glean

In the shell

Dotnet source can also be indexed directly from the Glean shell:

:index dotnet-scip DIR

The shell will pick a DB name and hash for you based on DIR.

Schema

The schema is in glean/schema/source/scip.angle

- + \ No newline at end of file diff --git a/docs/indexer/scip-python/index.html b/docs/indexer/scip-python/index.html index da9ea21a6..2468b9fb6 100644 --- a/docs/indexer/scip-python/index.html +++ b/docs/indexer/scip-python/index.html @@ -5,7 +5,7 @@ Python | Glean - + @@ -13,7 +13,7 @@

Python

To index Python we use SourceGraph's SCIP indexer for python. SCIP is a new format for tools to share information about code. Releases of scip-python can be installed with yarn or npm and used as indexers for SCIP, which Glean will accept. The indexer itself requires a python runtime.

Run the indexer

The indexer is run via the main glean CLI tool.

> cabal build exe:glean

And index your Python repository with:

glean index python-scip DIR --db NAME/INSTANCE

where

  • DIR is the root directory containing the Python project
  • name/hash is the name of the repository to create

Provide the usual --db-root and --schema or --service arguments to glean

In the shell

Python source can also be indexed directly from the Glean shell:

:index python-scip DIR

The shell will pick a DB name and hash for you based on DIR.

Schema

The schema is in glean/schema/source/scip.angle

- + \ No newline at end of file diff --git a/docs/introduction/index.html b/docs/introduction/index.html index 636abae10..0e79920e0 100644 --- a/docs/introduction/index.html +++ b/docs/introduction/index.html @@ -5,7 +5,7 @@ Introduction | Glean - + @@ -51,7 +51,7 @@ want to support. Usually that means things like the locations of definitions and cross-references, but not expressions.
  • If you're familiar with Datalog, it's worth noting that currently Angle is limited to non-recursive queries only.
  • - + \ No newline at end of file diff --git a/docs/query/api/haskell/index.html b/docs/query/api/haskell/index.html index a71f643f4..f739faad9 100644 --- a/docs/query/api/haskell/index.html +++ b/docs/query/api/haskell/index.html @@ -5,7 +5,7 @@ Haskell Query API | Glean - + @@ -23,7 +23,7 @@ request to Glean. This makes it efficient to do shallow queries and then selectively traverse and expand the results as needed.

    To use the API, import Glean.Haxl. The implementation of the API is in glean/haxl/Haxl/DataSource/Glean.hs.

    - + \ No newline at end of file diff --git a/docs/query/haskell/index.html b/docs/query/haskell/index.html index d28081d81..4b54c2b74 100644 --- a/docs/query/haskell/index.html +++ b/docs/query/haskell/index.html @@ -5,14 +5,14 @@ Haskell Query API | Glean - +
    - + \ No newline at end of file diff --git a/docs/query/intro/index.html b/docs/query/intro/index.html index 2517c4241..ff2796e75 100644 --- a/docs/query/intro/index.html +++ b/docs/query/intro/index.html @@ -5,7 +5,7 @@ Querying Glean | Glean - + @@ -27,7 +27,7 @@ that you can install in VS Code by following the instructions in the next section.

    Installing

    code --install-extension path/to/glean-x.y.z.vsix

    The VS Code documentation describes alternative ways to install an extension from a .vsix file, from within the editor, in case the above command does not work or a more graphical, user-friendly is preferable.

    - + \ No newline at end of file diff --git a/docs/running/index.html b/docs/running/index.html index 0bbd0599c..09c68c394 100644 --- a/docs/running/index.html +++ b/docs/running/index.html @@ -5,7 +5,7 @@ Running the Tools | Glean - + @@ -52,7 +52,7 @@ created, so it is likely to be a correct description of the data in the database.

  • --db-mock-writes
    Allow write operations, but discard the data and don't write it to the DB.

  • - + \ No newline at end of file diff --git a/docs/schema/all/index.html b/docs/schema/all/index.html index c45715dc0..fac7a10bc 100644 --- a/docs/schema/all/index.html +++ b/docs/schema/all/index.html @@ -5,7 +5,7 @@ The special "all" schema | Glean - + @@ -26,7 +26,7 @@ all separately, and clients can select at build time which version they want to use. This enables incremental migration of code from one schema to another schema.

    - + \ No newline at end of file diff --git a/docs/schema/basic/index.html b/docs/schema/basic/index.html index faa6e36ed..f1cbc7b3c 100644 --- a/docs/schema/basic/index.html +++ b/docs/schema/basic/index.html @@ -5,7 +5,7 @@ Basic Concepts | Glean - + @@ -23,7 +23,7 @@ patterns that match multiple keys, and get back all the facts that match the pattern. More about this when we talk about Angle queries.

    - + \ No newline at end of file diff --git a/docs/schema/changing/index.html b/docs/schema/changing/index.html index a59b2f0d2..55e4a6b28 100644 --- a/docs/schema/changing/index.html +++ b/docs/schema/changing/index.html @@ -5,7 +5,7 @@ How do I change a schema? | Glean - + @@ -14,7 +14,7 @@

    How do I change a schema?

    Glean supports modifying the schema directly, while providing backwards compatibility between existing clients and data across the schema change.

    Basic rules

    To preserve compatibility between clients and data, the schema can -only be changed in compatible ways. This means:

    • Adding or removing a field from a record, if the field has a defaultable type (see Default values)
    • Adding or removing an alternative from a sum type
    • Adding or removing a predicate or type declaration

    An example of an incompatible change would be changing the type of a +only be changed in compatible ways. This means:

    • Adding or removing a field from a record, if the field has a defaultable type (see Default values)
    • Adding or removing an alternative from a sum type
    • Adding or removing a predicate or type declaration
    • Changing a list to a set or vice versa

    An example of an incompatible change would be changing the type of a field, for example from nat to bool.

    Of course it's fine to make arbitrary changes to a schema that you're working on; the compatibility rules only come into effect when the schema is landed. From that point there might be existing clients and @@ -26,7 +26,7 @@ that Glean must be able to substitute a default value for the field when a client is using the new schema but the data is using the old schema, and there cannot be a default value for a predicate.

    Default values for missing fields are determined according to the -following table:

    TypeDefault value
    nat0
    byte0
    string""
    [T][]
    { field₁ : T₁, ..., fieldₙ : Tₙ }{ field₁ = default(T₁), ..., fieldₙ = default(Tₙ) }
    { field₁ : T₁ | ... | fieldₙ : Tₙ }{ field₁ = default(T₁) }
    boolfalse
    maybe Tnothing
    enum { name₁ | ... | nameₙ }name₁

    What if my schema changes are incompatible?

    If you don't care about backwards compatibility, then an easy +following table:

    TypeDefault value
    nat0
    byte0
    string""
    [T][]
    set Tthe empty set
    { field₁ : T₁, ..., fieldₙ : Tₙ }{ field₁ = default(T₁), ..., fieldₙ = default(Tₙ) }
    { field₁ : T₁ | ... | fieldₙ : Tₙ }{ field₁ = default(T₁) }
    boolfalse
    maybe Tnothing
    enum { name₁ | ... | nameₙ }name₁

    What if my schema changes are incompatible?

    If you don't care about backwards compatibility, then an easy workaround is to just bump the version of the whole schema, so for example if you have

    schema graphql.1 {
    ...
    }

    then change it to

    schema graphql.2 {
    ...
    }

    and make whatever changes you need. The new version of the schema is entirely separate from the old version as far as Glean is concerned, @@ -56,7 +56,7 @@ which can be useful if you want to perform schema changes in a more explicit way, or to rename schemas.

    The feature is enabled using a top-level directive

    schema my_schema.2 evolves my_schema.1

    This declaration has the effect of treating queries for my_schema.1 predicates as if they were for my_schema.2. That is the query results will be retrieved from the database in the shape of a my_schema.2 fact and transformed into a fact of the equivalent my_schema.1 predicate specified in the query.

    The new schema must contain all the predicates of the old schema, either with new versions or old versions, and their definitions must be backwards compatible. We can achieve this by copying the entire content of the old schema into the new one and modifying it there.

    Now what should Glean do when a client asks for a fact from an old schema?

    • Answer with db facts from the old schema
    • Answer with db facts from the new schema transformed into the old ones.

    If there are no facts of the old schema in in the database we will take option 2. If the database has any fact at all of the old schema we choose option 1.

    That is, schema evolutions only take effect if there are no facts of the old schema in the database; it is ignored otherwise.

    As an example suppose we start with the following schemas:

    schema src.1 {
    predicate File {
    path : string
    }
    }

    schema os.1 {
    import src.1

    predicate Permissions {
    file : File,
    permissions : nat
    }
    }

    schema info.1 {
    import src.1

    predicate IsTemporary {
    file : File
    } F where F = src.File { path = "/tmp".. }
    }

    Now we want to make a backward-compatible change to src.File and add an extension field. We could add this to the file:

    schema src.2 {
    predicate File {
    path : string,
    extension : string
    }
    }

    schema src.2 evolves src.1

    Now if the indexer is still producing only src.1 facts, all other predicates will work as before and queries for src.File.2 will return no results.

    Once the indexer is changed to produce only src.2 facts queries like src.File.1 _ will be fulfilled using data from the src.2 schema, converting the src.File.2 results to the shape of src.File.1 before returning to the client.

    This is also the case in the derivation query of info.IsTemporary. Although info imports src.1, the query will be transformed to use src.2 facts.

    On the other hand, os.Permissions will be empty. This must be the case because its first field references a src.File.1 fact, of which there is none in the database. For this predicate to continue being available we must evolve its schema as well.

    schema os.2 {             # changed
    import src.2 # changed

    predicate Permissions {
    file : File,
    permissions : nat
    }
    }

    schema os.2 evolves os.1 # changed
    - + \ No newline at end of file diff --git a/docs/schema/design/index.html b/docs/schema/design/index.html index 7044ed404..a625a0030 100644 --- a/docs/schema/design/index.html +++ b/docs/schema/design/index.html @@ -5,7 +5,7 @@ Schema Design | Glean - + @@ -30,29 +30,30 @@ stored only once. If the value (Type in the above example) is large, you should strongly consider using a key-value predicate.
  • Query performance

    • Both (1) and (2) support efficient querying by the key (Function in the example), and they both support slow filtering by the value -(Type).
  • Incrementality

    • These two alternatives are equivalent with respect to incrementality.
  • Using arrays

    If you're choosing between arrays and separate facts, then consider:

    • Arrays are ordered lists, whereas facts are just sets. If the order +(Type).

  • Incrementality

    • These two alternatives are equivalent with respect to incrementality.
  • Using arrays, sets or separate facts

    If you're choosing between arrays and separate facts, then consider:

    • Arrays are ordered lists, whereas facts are just sets. If the order of your items is important - because you're representing something that has an order, such as function arguments - then an array is the -right choice. (someday Glean might have a "set" type, but it -currently doesn't).

    • Conversely, if the order is not important, then using an array is +right choice.

    • Conversely, if the order is not important, then sets are the natural +choice. Using an array is a poor choice because you will be forced to choose an order when generating your data. If you don't have a deterministic way to pick the order, then your data representation is non-deterministic which leads to spurious differences in things like test outputs, which can -be annoying.

    • Arrays are much more compact than multiple facts. There can be a +be annoying.

    • Arrays and sets are much more compact than multiple facts. There can be a huge difference in storage overhead; it's worth measuring this for -your schema.

    • When a client fetches an array as part of the result of a query, -they will get the whole array. If your array is large, that may be a +your schema.

    • When a client fetches an array or a set as part of the result of a query, +they will get the whole array/set. If it is large, that may be a lot of data to send over the wire, and it might even result in an allocation limit error on the server, preventing the client from fetching the data at all. Facts tend to support incremental querying -better compared with arrays.

    • Facts with large arrays are also slower to search through in a query +better compared with arrays and sets.

    • Facts with large arrays/sets are also slower to search through in a query than smaller facts.

    Increase sharing

    If there is duplication in the data stored in our facts, we can often extract the common data into a predicate to increase sharing. One -example of this was described in What is the difference between a predicate and a type?.

    How to experiment with schema design

    + \ No newline at end of file diff --git a/docs/schema/recursion/index.html b/docs/schema/recursion/index.html index 6299bc87f..16494227c 100644 --- a/docs/schema/recursion/index.html +++ b/docs/schema/recursion/index.html @@ -5,7 +5,7 @@ Recursion | Glean - + @@ -23,7 +23,7 @@ keys would make this process significantly harder.

    Facts can be recursive in their values, but not their keys. A mutually recursive set of facts must be added to the database in a single batch, however.

    To summarise, recursion is

    • allowed between predicates
    • not allowed between keys
    • allowed between values
    - + \ No newline at end of file diff --git a/docs/schema/syntax/index.html b/docs/schema/syntax/index.html index ce1b1ce29..4859c87d6 100644 --- a/docs/schema/syntax/index.html +++ b/docs/schema/syntax/index.html @@ -5,7 +5,7 @@ Syntax | Glean - + @@ -44,7 +44,7 @@ future. The process for safely changing schemas is described in Changing the Schema.

    schema example.2 : example.1 {
    predicate Class :
    {
    # new definition of Class
    }
    }

    Inheritance is useful for making changes to a schema by creating a new schema version:

    • Inheriting from a schema brings into scope all the types and predicates of that schema, both qualified and unqualified.
    • The new schema also exports all the types and predicates defined in the schemas it inherits from, except those that are re-defined.

    Specifically, in the above example:

    • We can import example.2 anywhere and get all the predicates defined in example.1, except that we'll get the new Class defined in example.2.
    • We can still import example.1 and get the old version of the schema.

    Note that if you have predicates that depend on a predicate that was revised in this way, you must also copy those predicates to the new schema, because the existing predicates will refer to the old version of the predicate you revised. (In due course Glean will probably provide a convenient way to do this; in the meantime you have to copy & paste. Not a big deal because you'll usually delete the old one at some point, and you can't modify it anyway.)

    Named schemas can not form cycles through their import or inheritance declarations.

    Naming rules and conventions

    Names take the form of a dot-separated sequence of alphanumeric words. For example, sys.Blob, clang.File, or cxx.objc.Name. The words up to the last dot are the namespace, the final word is the name.

    See Names for full details.

    Briefly:

    • Namespaces (schema names) are dot-separated sequences of identifiers each beginning with a lower-case letter
    • Names and namespaces can contain only alphanumeric characters, '_', or '.' (namespaces only)
    • There is a set of reserved words that can't be used for names, e.g. class. Syncing the schema will fail with an error if you use a reserved word.
    - + \ No newline at end of file diff --git a/docs/schema/thrift/index.html b/docs/schema/thrift/index.html index 03cbee768..26b4305cf 100644 --- a/docs/schema/thrift/index.html +++ b/docs/schema/thrift/index.html @@ -5,7 +5,7 @@ Thrift and JSON | Glean - + @@ -19,8 +19,8 @@ and written directly. When you perform queries in the shell, the results are printed as JSON-encoded Thrift; when you write data to Glean it can be in the form of -JSON-encoded Thrift.

    The relationship between schema types and Thrift/JSON is given by the following table:

    Schema typeThrift typeJSON
    natNat (i64)123
    byteByte (i8)123
    stringstring"abc"
    boolbooltrue or false
    [byte]binarybase-64 encoded string *1
    [T]list<T>[...]
    {
      f₁ : T₁,
      ...,
      fₙ : Tₙ
    }
    struct Foo {
      1: T₁ f₁;
      ...
      n: Tₙ fₙ;
    }
    {
      "f₁" : q₁,
      ...
      "fₙ" : qₙ
    }
    {
      f₁ : T₁ |
      ... |
      fₙ : Tₙ
    }
    union Foo {
      1: T₁ f₁;
      ...
      n: Tₙ fₙ;
    }
    { "f" : t }
    for one of the fields f₁..fₙ
    maybe TIn a record field:
    optional T f
    f : t
    if the value is present
    enum {
      L₁|
      ...|
      Lₙ
    }
    enum Foo {
      L₁ = 1,
      ...
      Lₙ = n
    }
    the index of the value,
    e.g. 12
    predicate P : K -> Vstruct P {
      1: Id id
      2: optional K key
      3: optional V value
    }
    note*2
    refer to fact N:
    N or { "id": N }
    define a fact:
    { "id" : N,
       "key" : t } or
    { "key": t } or
    { "key": t,
        "value" : v }
    type N = Tdepending on T:
    struct N { .. }
    union N {...}
    enum N {...}
    typedef T N;
    same as type T
    1. The Thrift encoding of a binary field in JSON is a base-64-encoded string. However, not all Thrift implementations respect this. At the time of writing, the Python Thrift implementation doesn't base-64-encode binary values. For this reason we provide an option in the Glean Thrift API to disable base-64 encoding for binary if your client doesn't support it. The Glean Shell also uses this option to make it easier to work with binary.

    2. the key is optional - a nested fact may be expanded in place or represented by a reference to the fact ID only. When querying Glean data the query specifies which nested facts should be expanded in the result, and when writing data to Glean using Thrift or JSON, we can optionally specify the value of nested facts inline.

    - +JSON-encoded Thrift.

    The relationship between schema types and Thrift/JSON is given by the following table:

    Schema typeThrift typeJSON
    natNat (i64)123
    byteByte (i8)123
    stringstring"abc"
    boolbooltrue or false
    [byte]binarybase-64 encoded string *1
    [T]list<T>[...]
    set Tlist<T>[...]
    {
      f₁ : T₁,
      ...,
      fₙ : Tₙ
    }
    struct Foo {
      1: T₁ f₁;
      ...
      n: Tₙ fₙ;
    }
    {
      "f₁" : q₁,
      ...
      "fₙ" : qₙ
    }
    {
      f₁ : T₁ |
      ... |
      fₙ : Tₙ
    }
    union Foo {
      1: T₁ f₁;
      ...
      n: Tₙ fₙ;
    }
    { "f" : t }
    for one of the fields f₁..fₙ
    maybe TIn a record field:
    optional T f
    f : t
    if the value is present
    enum {
      L₁|
      ...|
      Lₙ
    }
    enum Foo {
      L₁ = 1,
      ...
      Lₙ = n
    }
    the index of the value,
    e.g. 12
    predicate P : K -> Vstruct P {
      1: Id id
      2: optional K key
      3: optional V value
    }
    note*2
    refer to fact N:
    N or { "id": N }
    define a fact:
    { "id" : N,
       "key" : t } or
    { "key": t } or
    { "key": t,
        "value" : v }
    type N = Tdepending on T:
    struct N { .. }
    union N {...}
    enum N {...}
    typedef T N;
    same as type T
    1. The Thrift encoding of a binary field in JSON is a base-64-encoded string. However, not all Thrift implementations respect this. At the time of writing, the Python Thrift implementation doesn't base-64-encode binary values. For this reason we provide an option in the Glean Thrift API to disable base-64 encoding for binary if your client doesn't support it. The Glean Shell also uses this option to make it easier to work with binary.

    2. the key is optional - a nested fact may be expanded in place or represented by a reference to the fact ID only. When querying Glean data the query specifies which nested facts should be expanded in the result, and when writing data to Glean using Thrift or JSON, we can optionally specify the value of nested facts inline.

    + \ No newline at end of file diff --git a/docs/schema/types/index.html b/docs/schema/types/index.html index cfe5bc266..27e5fcd64 100644 --- a/docs/schema/types/index.html +++ b/docs/schema/types/index.html @@ -5,14 +5,14 @@ Built-in Types | Glean - +
    -

    Built-in Types

    TypeMeaning
    nat64-bit natural numbers
    byte8-bit natural numbers
    stringUTF-8 encoded strings
    [T]lists of elements of type T
    { field₁ : T₁, ..., fieldₙ : Tₙ }a record with zero or more named fields
    { field₁ : T₁ | ... | fieldₙ : Tₙ }a sum (union) type with one or more named alternatives
    Pa reference to a fact of predicate P
    boolthe boolean type with values true and false
    maybe Tan optional value of type T
    enum { name₁ | ... | nameₙ }exactly one of the symbols name₁..nameₙ
    - +

    Built-in Types

    TypeMeaning
    nat64-bit natural numbers
    byte8-bit natural numbers
    stringUTF-8 encoded strings
    [T]lists of elements of type T
    set T set of elements of type T
    { field₁ : T₁, ..., fieldₙ : Tₙ }a record with zero or more named fields
    { field₁ : T₁ | ... | fieldₙ : Tₙ }a sum (union) type with one or more named alternatives
    Pa reference to a fact of predicate P
    boolthe boolean type with values true and false
    maybe Tan optional value of type T
    enum { name₁ | ... | nameₙ }exactly one of the symbols name₁..nameₙ
    + \ No newline at end of file diff --git a/docs/schema/workflow/index.html b/docs/schema/workflow/index.html index 507347991..1209dd72f 100644 --- a/docs/schema/workflow/index.html +++ b/docs/schema/workflow/index.html @@ -5,7 +5,7 @@ Workflow | Glean - + @@ -15,7 +15,7 @@ glean/schema/thrift, which are then processed into Haskell code by

    make thrift-schema-hs

    and finally built by

    make glean

    Examples of code using these types:

    Experimenting with schemas

    1. Modify the source files in glean/schema/source

    2. Start up the shell locally using your schema:
      glean shell --db-root ~/local/gleandb --schema glean/schema/source
      If you don't already have a ~/local/gleandb for storing local DBs, create it with mkdir ~/local/gleandb.

    3. Test it with some example data: see Loading a DB from JSON in the shell.

    4. Iterate as necessary, using :reload in the shell to reload the schema.

    - + \ No newline at end of file diff --git a/docs/server/index.html b/docs/server/index.html index 37075b4c2..ac0f757bd 100644 --- a/docs/server/index.html +++ b/docs/server/index.html @@ -5,7 +5,7 @@ Running the Glean Server | Glean - + @@ -17,7 +17,7 @@ Port number to listen on.

    The server watches for changes in any configuration files specified with config:PATH, including the schema.

    - + \ No newline at end of file diff --git a/docs/shell/index.html b/docs/shell/index.html index 908937786..d4027b9b8 100644 --- a/docs/shell/index.html +++ b/docs/shell/index.html @@ -5,7 +5,7 @@ Using the Shell | Glean - + @@ -57,7 +57,7 @@ test your changes.
  • :statistics [PREDICATE]
    Show statistics for the current database.
  • :quit
    Leave the shell.
  • - + \ No newline at end of file diff --git a/docs/trying/index.html b/docs/trying/index.html index 7aff9f9d7..d04f77cf1 100644 --- a/docs/trying/index.html +++ b/docs/trying/index.html @@ -5,7 +5,7 @@ Trying Glean | Glean - + @@ -28,7 +28,7 @@ (http://localhost:8888/packages/react-dom/src/client/ReactDOMComponent.js) - note how Glean is accurately linking both local and imported symbols.

    - + \ No newline at end of file diff --git a/docs/walkthrough/index.html b/docs/walkthrough/index.html index ff85ba3ee..02be63e1d 100644 --- a/docs/walkthrough/index.html +++ b/docs/walkthrough/index.html @@ -5,7 +5,7 @@ Walkthrough | Glean - + @@ -22,7 +22,7 @@ in /tmp/glean/facts.glean. Then reload schema and create a database from the example data using :reload and :load <file> in the shell:

    > :reload
    reloading schema [2 schemas, 7 predicates]
    > :load /tmp/glean/facts.glean
    facts>

    Now head over to Angle Guide to try some example queries and learn about how the query language works.

    - + \ No newline at end of file diff --git a/docs/write/index.html b/docs/write/index.html index 4bf6b9f73..79f453e6d 100644 --- a/docs/write/index.html +++ b/docs/write/index.html @@ -5,7 +5,7 @@ Writing data to Glean | Glean - + @@ -22,7 +22,7 @@ have dependencies between them, so the server won't hand out a task until its dependencies are complete.

  • When all tasks are done, the server marks the database as complete.

  • APIs for writing

    If none of the above work for you, the Thrift API enable basic write access to the database.

    • kickOff can be used to create a new DB
    • sendJsonBatch is for sending facts in JSON-serialized form
    • finishBatch exposes the result of a previously sent JSON batch
    • workFinished closes a DB

    A rough outline of a client looks like:

    glean = make_glean_thrift_client()
    db_handle = make_uuid()
    glean.kickOff(my_repo, KickOffFill(writeHandle=db_handle))
    for json_batch in json_batches:
    handle = glean.sendJsonBatch(json_batch)
    result = glean.finishBatch(handle)
    # handle result
    glean.workFinished(my_repo, db_handle, success_or_failure)

    Writing from the command line

    JSON format

    The JSON format for Glean data is described in Thrift and JSON.

    Here's an example of JSON data for writing to Glean:

    [
    { "predicate": "cxx1.Name.1", # define facts for cxx1.Name.1
    "facts": [
    { "id": 1, "key": "abc" }, # define a fact with id 1
    { "id": 2, "key": "def" }
    ]
    },
    { "predicate": "cxx1.FunctionName.1", # define facts for cxx1.FunctionName.1
    "facts": [
    { "id": 3,
    "key": {
    "name": { "id": 1 }}} # reference to fact with id 1
    ]
    },
    { "predicate": "cxx1.FunctionQName.1", # define facts for cxx1.FunctionQName.1
    "facts": [
    { "key": {
    "name": 3, # 3 is shorthand for { "id": 3 }
    "scope": { "global_": {} } } },
    { "key": {
    "name": {
    "key": { # define a nested fact directly
    "name": {
    "key": "ghi" }}}, # another nested fact
    "scope": {
    "namespace_": {
    "key": {
    "name": {
    "key": "std" }}}}}
    ]
    }
    ]

    The rules of the game are:

    • Predicate names must include versions, i.e. cxx1.Name.1 rather than cxx1.Name.
    • The id field when defining a fact is optional. The id numbers in the input file will not be the final id numbers assigned to the facts in the database.
    • There are no restrictions on id values (any 64-bit integer will do) but an id value may not be reused within a file.
    • Later facts may refer to earlier ones using either { "id": N } or just N.
    • It is only possible to refer to ids from facts in the same file, if you are writing multiple files using glean write or via the sendJsonBatch API.
    • a nested facts can be defined inline, instead of defining it with an id first and then referencing it.
    • an inline nested fact can be given an id and referred to later.

    Loading a DB from JSON in the shell

    The shell is useful for experimenting with creating a DB from JSON data directly. Let's try loading the data above into a DB in the shell:

    $ mkdir /tmp/glean
    $ glean shell --db-root /tmp/glean
    Glean Shell, dev mode
    type :help for help.
    no fbsource database availabe
    > :load test/0 /home/smarlow/test
    I0514 01:19:37.137109 3566745 Work.hs:184] test/16: database complete

    Let's see what facts we loaded:

    test> :stat
    1
    count: 72
    size: 5988
    cxx1.FunctionName.1
    count: 2
    size: 66
    cxx1.FunctionQName.1
    count: 2
    size: 70
    cxx1.Name.1
    count: 4
    size: 148
    cxx1.NamespaceQName.1
    count: 1
    size: 35
    test>

    Note that there were 4 cxx1.Name.1 facts - some of those were defined as inline nested facts in the JSON. We can query them all:

    test> cxx1.Name _
    4 results, 1 queries, 4 facts, 0.22ms, 44296 bytes

    { "id": 1096, "key": "abc" }
    { "id": 1097, "key": "def" }
    { "id": 1100, "key": "ghi" }
    { "id": 1102, "key": "std" }

    Note that the id values here do not correspond to the id values in the input file.

    Creating a database using the command line

    The glean command-line tool can be used to create a database directly on the server.

    To create a database from a single file of JSON facts:

    glean create --service <write-server> --finish --db <name>/<instance> <filename>

    where

    • <write-server> is the host:port of the Glean server
    • <name> is the name for your DB. For indexing repositories we normally use the name of the repository, but it's just a string, so you can use whatever you want.
    • <hash> identifies this particular instance of your database. For repositories we normally use the revision hash, but, again, it's just a string.
    • <filename> the file containing the JSON facts.

    If the file is more than, say, 100MB, this operation will probably time out sending the data to the server. To send large amounts of data you need to batch it up into multiple files, and then send it like this:

    glean create --service <write-server> --db <name>/<hash>
    glean write --service <write-server> --db <name>/<hash> <filename1>
    glean write --service <write-server> --db <name>/<hash> <filename2>
    ...
    glean finish --service <write-server> --db <name>/<hash>

    To find out if your DB made it:

    glean shell --service <write-server> :list

    This will list the DBs available on the write server.

    - + \ No newline at end of file diff --git a/index.html b/index.html index 87ad09a79..5819ed72c 100644 --- a/index.html +++ b/index.html @@ -5,14 +5,14 @@ Glean | Glean - +
    Glean Logo

    Glean

    System for collecting, deriving and querying facts about source code

    Key Features

    Rich types

    Store detailed information about code

    Compact storage

    Store data about code at scale

    Efficient queries

    Build experiences with deep insights from code

    - + \ No newline at end of file