1659693600
Nos propusimos diseñar un generador de consultas para TypeScript que pueda expresar consultas de complejidad arbitraria mientras infiere el tipo de devolución automáticamente.
No fue fácil, pero con la ayuda de algunas de las funciones modernas más geniales de JavaScript y algunos trucos oscuros de TypeScript, creemos que lo logramos. El resultado es algo que ofrece lo mejor de todos los mundos: la seguridad de tipo de un ORM junto con el poder expresivo de un lenguaje de consulta completo.
¡Ya puedes jugar con el generador de consultas! Recomendamos clonar nuestro repositorio de sandbox de MCU y seguir las instrucciones para inicializar el proyecto; debería tomar menos de un minuto.
Nota importante: este es un generador de consultas para EdgeQL , el lenguaje de consulta orientado a objetos de EdgeDB. Está diseñado como un sucesor espiritual de SQL y resuelve algunos de sus mayores problemas de usabilidad. A lo largo de esta publicación, haga clic en la pestaña "EdgeQL" para ver el equivalente de EdgeQL de cada expresión del generador de consultas.
Mecanografiado
const query = e.select(e.Movie, movie => ({
id: true,
title: true,
actors: {
name: true,
},
filter: e.op(movie.title, '=', 'Iron Man 3')
}));
// inferred type:
// { id: string; title: string; actors: { name: string }[]}
EdgeQL
select Movie {
id,
title,
actors: {
name
}
}
filter .title = "Iron Man 3"
Tenga en cuenta la "forma de selección" estilo GraphQL para especificar qué campos buscar. Como era de esperar, esta consulta devuelve un resultado similar a JSON estructurado, no una lista plana de filas.
{
"id": "9278d96e-1932-44e4-a9b3-34e49b592c26",
"title": "Iron Man 3",
"release_year": 2013,
"actors": [
{ "name": "Robert Downey Jr." },
{ "name": "Gwyneth Paltrow" },
{ "name": "Ben Kingsley" }
]
}
La consulta anterior asume el siguiente esquema, tal como se define con el lenguaje de definición de esquemas de EdgeDB .
type Movie {
required property title -> str;
property release_year -> int64;
multi link actors -> Person;
}
type Person {
required property name -> str;
}
Generación del generador de consultas
Para comenzar con el generador de consultas, deberá activar una instancia de EdgeDB. La forma más sencilla de hacerlo es instalar la CLI de EdgeDB y ejecutarla edgedb project init
en el directorio de su proyecto. Siga el inicio rápido para una introducción más completa.
Luego instale el edgedb
paquete desde NPM y ejecute el siguiente npx
comando.
$ npm install edgedb
$ npx edgeql-js
Este comando hace una introspección de su base de datos y genera un puñado de archivos en el dbschema/edgeql-js
directorio. (Por convención, dbschema
es el directorio que se usa para almacenar todo lo relacionado con EdgeDB, como archivos de migración y de esquema). Recomendamos importar el generador de consultas como una sola variable llamada e
.
import e from "./dbschema/edgeql-js";
Esta variable contiene todo lo que necesita para definir consultas EdgeQL arbitrariamente complejas, pero empecemos poco a poco: una consulta "Hello World".
import e from "./dbschema/edgeql-js";
const query = e.select("hello world!");
La e.select
función devuelve un objeto que representa una consulta de EdgeQL; nos referiremos a esto como una "expresión del generador de consultas" o simplemente "expresión".
Para ejecutar la expresión, pase un cliente.run()
al método de la expresión .
La createClient
función devuelve una instancia de Client
: una clase que administra un conjunto de conexiones a su instancia de EdgeDB y proporciona una API simple para ejecutar consultas.
import {createClient} from "edgedb";
import e from "./dbschema/edgeql-js";
const client = createClient();
const query = e.select("Hello world!");
const result = await query.run(client);
// => "Hello world!"
El .run
método devuelve una Promesa fuertemente tipada; el generador de consultas infiere automáticamente el tipo de retorno de todas las expresiones. En el ejemplo anterior result
tiene un string
tipo. Puede extraer este tipo con el $infer
ayudante.
import {createClient} from "edgedb";
import e, {$infer} from "./dbschema/edgeql-js";
const client = createClient();
const query = e.select("Hello world!");
type query = $infer<typeof query>;
// string
Empecemos a ver algunas consultas no triviales.
Utilice la e.insert
función para escribir insert
consultas.
Mecanografiado
e.insert(e.Movie, {
title: "Doctor Strange in the Multiverse of Madness",
release_year: 2022
});
EdgeQL
insert Movie {
title := "Doctor Strange in the Multiverse of Madness",
release_year := 2022
}
El primer argumento es un tipo de objeto ; estos son generados automáticamente por el generador de consultas. El segundo argumento contiene los datos a insertar. Tenga en cuenta que no estamos incluyendo una id
propiedad; que EdgeDB genera automáticamente.
Dado que la title
propiedad tiene tipo str
, e.insert
naturalmente espera un valor de cadena. De manera similar , release_year
tiene tipo int64
, por lo que espera un número. La siguiente tabla asigna cada tipo escalar de EdgeDB a su equivalente de TypeScript más cercano.
Tipo EdgeDB | tipo de JavaScript |
str | string |
bool | boolean |
float32 float64 int16 int32 int64 | number |
json | string |
uuid | string |
bigint | BigInt |
decimal | N/A (no compatible) |
bytes | Buffer |
datetime | Date |
duration | Duration() |
cal::local_date | LocalDate() |
cal::local_time | LocalTime() |
cal::local_datetime | LocalDateTime() |
Para ciertos tipos como duration
ese que no tienen equivalente en JavaScript, hemos implementado clases personalizadas para representar ese tipo de datos.
Al igual que en EdgeQL, las subconsultas son completamente indoloras; para hacer inserciones anidadas, simplemente suelte una e.insert
dentro de otra.
Mecanografiado
e.insert(e.Movie, {
title: "Iron Man",
release_year: 2008,
actors: e.set(
e.insert(e.Person, { name: "Robert Downey Jr." }),
e.insert(e.Person, { name: "Gwyneth Paltrow" })
),
});
EdgeQL
insert Movie {
title := "Iron Man",
release_year : 2008,
actors := {
(insert Person { name := "Robert Downey Jr." }),
(insert Person { name := "Gwyneth Paltrow" })
}
}
Arriba, estamos usando la e.set
función para definir un conjunto literal . En EdgeQL esto se indica con llaves:select {'a', 'b', 'c'}
Ahora en la carne y las papas: seleccionando objetos . Comencemos seleccionando todas las películas en la base de datos.
Mecanografiado
const query = e.select(e.Movie, () => ({
id: true,
title: true
}));
const result = await query.run(client);
// {id: string; title: string;}[]
EdgeQL
select Movie {
id,
title,
release_year
}
Como forma abreviada de seleccionar todas las propiedades de un objeto, utilice el operador de extensión junto con la *
propiedad especial. Esta es una característica del generador de consultas sin equivalente de EdgeQL (todavía); Plain EdgeQL no es compatible con select *
la funcionalidad.
const query = e.select(e.Movie, () => ({
...e.Movie['*']
}));
const result = await query.run(client);
/* {
id: string;
title: string;
release_year: number | null;
}[] */
Como era de esperar, el tipo de release_year
propiedad es number | null
una propiedad opcional.
Las formas se pueden anidar para obtener objetos vinculados, como actors
.
Mecanografiado
const query = e.select(e.Movie, () => ({
title: true,
actors: {
name: true,
}
}));
const result = await query.run(client);
// { title: string, actors: {name: string}[] }[]
EdgeQL
select Movie {
title,
actors: {
name
}
}
En este punto, puede que se pregunte por qué el segundo argumento e.select
es una función en lugar de un objeto simple. Bueno: el generador de consultas puede hacer mucho más que simples conjuntos de selección de estilo GraphQL. Para empezar, puede definir propiedades calculadas .
Mecanografiado
const query = e.select(e.Movie, (movie) => ({
title: true,
title_upper: e.str_upper(movie.title),
cast_size: e.count(movie.actors)
}));
const result = await query.run(client);
// { title: string; title_upper: string; cast_size: number }[]
EdgeQL
select Movie {
title,
title_upper := str_upper(.title),
cast_size := count(.actors)
}
Nuestra "función de forma" ahora tiene un argumento: movie
. Esta variable representa el “alcance”; podemos usarlo para hacer referencia a las propiedades y enlaces de los usuarios que estamos seleccionando. En este caso, estamos definiendo algunas expresiones calculadas simples usando dos funciones integradas: e.count
y e.str_upper
; el generador de consultas refleja toda la biblioteca estándar de EdgeDB .
Ah, y tenga en cuenta que el generador de consultas infirió correctamente el tipo de title_upper
y cast_size
! El resultado de esta consulta sería algo como esto:
[
{
title: "Iron Man",
title_upper: "IRON MAN",
cast_size: 2
},
// etc.
]
Para agregar una filter
cláusula a su consulta de selección, incluya la filter
clave especial en su forma. Esta clave espera una expresión booleana; lo más común es que esta expresión de voluntad incluya un operador como =
, >=
, ++
, not
y or
; los operadores se expresan con la e.op
función.
A continuación, seleccionamos todas las películas con un título que contiene "matriz" (sin distinción entre mayúsculas y minúsculas).
Mecanografiado
e.select(e.Movie, (movie) => ({
title: true,
release_year: true,
filter: e.op(movie.title, "ilike", "%matrix%"),
}));
EdgeQL
select Movie {
title,
release_year
} filter .title ilike "%matrix%"
Dependiendo de su tipo EdgeDB asociado , las expresiones pueden tener varias propiedades y métodos. Por ejemplo, las expresiones correspondientes a str
valores (como las movie.title
anteriores) se pueden indexar y dividir fácilmente. (Esto también se aplica a las expresiones array
, tuple
y ).json
¡Recuerda que movie.title
no es una cuerda! Es un objeto que representa una consulta que devuelve una cadena. Además, movie.title[0]
devuelve otra expresión más. El generador de consultas implementa esta "indexación mágica" con la ayuda de la API de proxy .
Podemos usar esto para seleccionar todas las películas que comienzan con la letra "A".
Mecanografiado
e.select(e.Movie, (movie) => ({
title: true,
release_year: true,
filter: e.op(movie.title[0], "=", "A"),
}));
EdgeQL
select Movie {
title,
release_year
} filter .title[0] = "A"
En este punto, puede estar pensando que la forma se está amontonando un poco. ¿Por qué usamos un solo objeto para definir nuestra selección de campos, propiedades calculadas y filtros? ¿No habrá conflictos clave?
¡En realidad no! Esta es una decisión muy intencional. EdgeQL reserva ciertas palabras clave como "filtro", por lo que no se puede usar fácilmente como propiedad o nombre de enlace. En cuanto a los campos calculados, no se les permite "sobrescribir" una propiedad/enlace dentro de una forma de selección; TypeScript (y EdgeQL) generará un error.
Con esta API, cada capa de profundidad de consulta corresponde a una única capa de anidamiento de objetos.
e.select(e.Movie, (movie) => ({ id: true, title: true, actors: (actor) => ({ name: true, filter: e.op(actor.name, "ilike", "chris") }), filter: e.op(movie.release_year, "=", 2022) }));
Compare esto con la sintaxis más detallada de los ORM de JavaScript modernos. Prisma requiere dos capas de anidamiento de objetos para cada capa adicional de profundidad de consulta. Esta es la misma consulta expresada con el cliente Prisma.
prisma.movie.findMany({
where: {
release_year: {
eq: 2022
}
},
select: {
id: true,
title: true,
actors: {
select: {
name: true
},
where: {
name: {
contains: "chris",
mode: "insensitive"
}
}
}
}
});
El generador de consultas es lo suficientemente inteligente como para saber cuándo intenta seleccionar un solo objeto. Por ejemplo:
const query = e.select(e.Movie, (movie) => ({
title: true,
filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
}));
const result = await query.run(client);
// { title: string } | null
El tipo de resultado inferido es { title: string } | null
. Si, en cambio, filtra por una propiedad no exclusiva como release_year
, el resultado será una matriz.
const query = e.select(e.Movie, (movie) => ({
title: true,
filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
filter: e.op(movie.release_year, '=', 2022)
}));
const result = await query.run(client);
// { title: string }[]
El generador de consultas detecta cuando filtra una propiedad con una restricción exclusiva (unicidad) (por ejemplo, .id
) con el operador de igualdad ( =
). En estas circunstancias, la consulta solo puede devolver cero o un objeto; esto se refleja en el tipo inferido. Por lo tanto, no hay necesidad de API separadas para .findOne
y .findMany
: el generador de consultas puede resolverlo.
La order_by
tecla especial se puede usar para especificar operaciones de orden en el resultado de select
y limit
/ offset
se puede usar para la paginación.
Mecanografiado
e.select(e.Movie, (movie) => ({
title: true,
order_by: e.count(movie.actors),
limit: 10,
offset: 40
}));
EdgeQL
select Movie {
title
}
order by count(.actors)
limit 10
offset 40
La order_by
llave admite pedidos compuestos con instrucciones personalizables y políticas de manejo de vacíos.
e.select(e.Movie, (movie) => ({
title: true,
order_by: [
{
expression: e.count(movie.actors),
direction: e.ASC,
empty: e.EMPTY_LAST,
},
{
expression: movie.title,
direction: e.DESC,
}
]
}));
Mecanografiado
e.update(e.Movie, (movie) => ({
filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
set: {
title: 'Avengers: Endgame',
},
}));
EdgeQL
e.update(e.Movie, (movie) => ({
filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
set: {
title: 'Avengers: Endgame',
},
}));
El generador de consultas es particularmente útil (o más bien, los ORM son particularmente malos) cuando se establecen propiedades en una versión modificada de su valor actual. Por ejemplo, esta consulta eliminaría los espacios en blanco adicionales de todos los títulos de películas.
Mecanografiado
e.update(e.Movie, (movie) => ({
set: {
title: e.str_trim(movie.title),
},
}));
EdgeQL
update Movie
set {
title := str_trim(.title)
}
Con un ORM, esto es inexpresable; necesitaría escribir un script para iterar a través de todas las películas en su base de datos.
Al actualizar enlaces , el generador de consultas admite una sintaxis especial para agregar o sustraer del conjunto de objetos vinculados.
Mecanografiado
const actors = e.select(e.Person, person => ({
filter: e.op(person.name, 'in', e.set('Benedict Cumberbatch', 'Rachel McAdams'))
}));
const query = e.update(e.Movie, (movie) => ({
filter: e.op(movie.title, '=', "Doctor Strange"),
set: {
actors: {'+=': actors},
}
})).run(client);
EdgeQL
with actors := (
select Person
filter .name in {'Benedict Cumberbatch', 'Rachel McAdams'}
)
update Movie
filter .title = "Doctor Strange"
set {
actors += actors
}
El update
ejemplo anterior también demuestra una de las mayores fortalezas del generador de consultas: la composicionalidad . Debido a que la declaración y ejecución de consultas son dos pasos distintos, es posible declarar partes de una consulta por separado y luego juntarlas todas más tarde. Escribir consultas complejas es como escribir un guión.
Por ejemplo, podemos componer varias expresiones para realizar una inserción + selección anidada en una consulta.
Mecanografiado
const rdj = e.insert(e.Person, {
name: "Robert Downey Jr."
});
const ironMan = e.insert(e.Movie, {
title: "Iron Man",
release_year: 2008,
actors: rdj
});
const query = e.select(ironMan, () => ({
title: true,
release_year: true,
num_actors: e.count(ironMan.actors)
}));
const result = await query.run(client);
// {title: string; release_year: number; num_actors: number}
EdgeQL
with
rdj := (
insert Person {
name := "Robert Downey Jr."
}
),
ironMan := (
insert Movie {
title := "Iron Man",
release_year := 2008
}
)
select ironMan {
title,
release_year,
num_actors := count(.actors)
};
El generador de consultas detecta que newMovie
ocurre varias veces dentro query
y lo extrae en un with
bloque (también conocido como "expresión de tabla común" en lenguaje SQL). Tenga en cuenta que solo hay uno await
. No estamos ejecutando rdj
y ironMan
; son subconsultas que se componen en la "superconsulta" final, que se puede ejecutar en un solo viaje de ida y vuelta a la base de datos.
Como guinda final, el generador de consultas facilita la parametrización de sus consultas. Esto le permite usar datos externos (por ejemplo, el cuerpo de una POST
solicitud entrante) de forma segura.
Mecanografiado
const query = e.params(
{ title: e.str, release_year: e.int64 },
($) => {
return e.insert(e.Movie, {
title: $.title,
release_year: $.release_year,
});
}
);
const result = await query.run(client, {
title: 'Thor: Love and Thunder',
release_year: 2022,
});
EdgeQL
with
title := <str>$title,
release_year := <int64>$release_year
insert Movie {
title := title,
release_year := release_year
}
Para una consulta parametrizada, pasa los parámetros como segundo argumento a .run()
; están fuertemente tipados y validados en tiempo de ejecución.
Esperemos que quede claro a partir de los ejemplos anteriores, pero en términos de poder expresivo, el generador de consultas está más allá de todos los ORM que conocemos. En general, los ORM solo pueden representar operaciones de lectura/escritura relativamente básicas, mientras que un lenguaje de consulta adecuado puede expresar mucho más:
??
en JavaScript)a ? b : c
en JavaScript)union
oin
Esto es por no hablar del modelado de esquemas. EdgeDB admite aún más funciones que faltan en la mayoría de los ORM:
int{16,|32|64}
, float{32|64}
, bigint
y decimal
)duration
fechas y horas no conscientes de la zona horariaEl generador de consultas ha estado disponible desde el lanzamiento de EdgeDB 1.0 en febrero de 2022 y es estable y está listo para la producción. El generador de consultas se actualizó recientemente para admitir todas las características de EdgeDB 2.0, como la e.group
declaración y las variables de esquema global. Esta publicación solo cubre un subconjunto de la funcionalidad completa del generador de consultas; consulte la Documentación para una mirada más completa.
Lanzaremos EdgeDB 2.0 durante un evento de lanzamiento transmitido en vivo el 28 de julio de 2022. Únase a nosotros para algunas charlas relámpago sobre las nuevas funciones más importantes y la primera demostración pública de la nueva interfaz de usuario de administración de EdgeDB.
Fuente: https://www.edgedb.com/blog/designing-the-ultimate-typescript-query-builder
#typescript #EdgeDb
1659693600
Nos propusimos diseñar un generador de consultas para TypeScript que pueda expresar consultas de complejidad arbitraria mientras infiere el tipo de devolución automáticamente.
No fue fácil, pero con la ayuda de algunas de las funciones modernas más geniales de JavaScript y algunos trucos oscuros de TypeScript, creemos que lo logramos. El resultado es algo que ofrece lo mejor de todos los mundos: la seguridad de tipo de un ORM junto con el poder expresivo de un lenguaje de consulta completo.
¡Ya puedes jugar con el generador de consultas! Recomendamos clonar nuestro repositorio de sandbox de MCU y seguir las instrucciones para inicializar el proyecto; debería tomar menos de un minuto.
Nota importante: este es un generador de consultas para EdgeQL , el lenguaje de consulta orientado a objetos de EdgeDB. Está diseñado como un sucesor espiritual de SQL y resuelve algunos de sus mayores problemas de usabilidad. A lo largo de esta publicación, haga clic en la pestaña "EdgeQL" para ver el equivalente de EdgeQL de cada expresión del generador de consultas.
Mecanografiado
const query = e.select(e.Movie, movie => ({
id: true,
title: true,
actors: {
name: true,
},
filter: e.op(movie.title, '=', 'Iron Man 3')
}));
// inferred type:
// { id: string; title: string; actors: { name: string }[]}
EdgeQL
select Movie {
id,
title,
actors: {
name
}
}
filter .title = "Iron Man 3"
Tenga en cuenta la "forma de selección" estilo GraphQL para especificar qué campos buscar. Como era de esperar, esta consulta devuelve un resultado similar a JSON estructurado, no una lista plana de filas.
{
"id": "9278d96e-1932-44e4-a9b3-34e49b592c26",
"title": "Iron Man 3",
"release_year": 2013,
"actors": [
{ "name": "Robert Downey Jr." },
{ "name": "Gwyneth Paltrow" },
{ "name": "Ben Kingsley" }
]
}
La consulta anterior asume el siguiente esquema, tal como se define con el lenguaje de definición de esquemas de EdgeDB .
type Movie {
required property title -> str;
property release_year -> int64;
multi link actors -> Person;
}
type Person {
required property name -> str;
}
Generación del generador de consultas
Para comenzar con el generador de consultas, deberá activar una instancia de EdgeDB. La forma más sencilla de hacerlo es instalar la CLI de EdgeDB y ejecutarla edgedb project init
en el directorio de su proyecto. Siga el inicio rápido para una introducción más completa.
Luego instale el edgedb
paquete desde NPM y ejecute el siguiente npx
comando.
$ npm install edgedb
$ npx edgeql-js
Este comando hace una introspección de su base de datos y genera un puñado de archivos en el dbschema/edgeql-js
directorio. (Por convención, dbschema
es el directorio que se usa para almacenar todo lo relacionado con EdgeDB, como archivos de migración y de esquema). Recomendamos importar el generador de consultas como una sola variable llamada e
.
import e from "./dbschema/edgeql-js";
Esta variable contiene todo lo que necesita para definir consultas EdgeQL arbitrariamente complejas, pero empecemos poco a poco: una consulta "Hello World".
import e from "./dbschema/edgeql-js";
const query = e.select("hello world!");
La e.select
función devuelve un objeto que representa una consulta de EdgeQL; nos referiremos a esto como una "expresión del generador de consultas" o simplemente "expresión".
Para ejecutar la expresión, pase un cliente.run()
al método de la expresión .
La createClient
función devuelve una instancia de Client
: una clase que administra un conjunto de conexiones a su instancia de EdgeDB y proporciona una API simple para ejecutar consultas.
import {createClient} from "edgedb";
import e from "./dbschema/edgeql-js";
const client = createClient();
const query = e.select("Hello world!");
const result = await query.run(client);
// => "Hello world!"
El .run
método devuelve una Promesa fuertemente tipada; el generador de consultas infiere automáticamente el tipo de retorno de todas las expresiones. En el ejemplo anterior result
tiene un string
tipo. Puede extraer este tipo con el $infer
ayudante.
import {createClient} from "edgedb";
import e, {$infer} from "./dbschema/edgeql-js";
const client = createClient();
const query = e.select("Hello world!");
type query = $infer<typeof query>;
// string
Empecemos a ver algunas consultas no triviales.
Utilice la e.insert
función para escribir insert
consultas.
Mecanografiado
e.insert(e.Movie, {
title: "Doctor Strange in the Multiverse of Madness",
release_year: 2022
});
EdgeQL
insert Movie {
title := "Doctor Strange in the Multiverse of Madness",
release_year := 2022
}
El primer argumento es un tipo de objeto ; estos son generados automáticamente por el generador de consultas. El segundo argumento contiene los datos a insertar. Tenga en cuenta que no estamos incluyendo una id
propiedad; que EdgeDB genera automáticamente.
Dado que la title
propiedad tiene tipo str
, e.insert
naturalmente espera un valor de cadena. De manera similar , release_year
tiene tipo int64
, por lo que espera un número. La siguiente tabla asigna cada tipo escalar de EdgeDB a su equivalente de TypeScript más cercano.
Tipo EdgeDB | tipo de JavaScript |
str | string |
bool | boolean |
float32 float64 int16 int32 int64 | number |
json | string |
uuid | string |
bigint | BigInt |
decimal | N/A (no compatible) |
bytes | Buffer |
datetime | Date |
duration | Duration() |
cal::local_date | LocalDate() |
cal::local_time | LocalTime() |
cal::local_datetime | LocalDateTime() |
Para ciertos tipos como duration
ese que no tienen equivalente en JavaScript, hemos implementado clases personalizadas para representar ese tipo de datos.
Al igual que en EdgeQL, las subconsultas son completamente indoloras; para hacer inserciones anidadas, simplemente suelte una e.insert
dentro de otra.
Mecanografiado
e.insert(e.Movie, {
title: "Iron Man",
release_year: 2008,
actors: e.set(
e.insert(e.Person, { name: "Robert Downey Jr." }),
e.insert(e.Person, { name: "Gwyneth Paltrow" })
),
});
EdgeQL
insert Movie {
title := "Iron Man",
release_year : 2008,
actors := {
(insert Person { name := "Robert Downey Jr." }),
(insert Person { name := "Gwyneth Paltrow" })
}
}
Arriba, estamos usando la e.set
función para definir un conjunto literal . En EdgeQL esto se indica con llaves:select {'a', 'b', 'c'}
Ahora en la carne y las papas: seleccionando objetos . Comencemos seleccionando todas las películas en la base de datos.
Mecanografiado
const query = e.select(e.Movie, () => ({
id: true,
title: true
}));
const result = await query.run(client);
// {id: string; title: string;}[]
EdgeQL
select Movie {
id,
title,
release_year
}
Como forma abreviada de seleccionar todas las propiedades de un objeto, utilice el operador de extensión junto con la *
propiedad especial. Esta es una característica del generador de consultas sin equivalente de EdgeQL (todavía); Plain EdgeQL no es compatible con select *
la funcionalidad.
const query = e.select(e.Movie, () => ({
...e.Movie['*']
}));
const result = await query.run(client);
/* {
id: string;
title: string;
release_year: number | null;
}[] */
Como era de esperar, el tipo de release_year
propiedad es number | null
una propiedad opcional.
Las formas se pueden anidar para obtener objetos vinculados, como actors
.
Mecanografiado
const query = e.select(e.Movie, () => ({
title: true,
actors: {
name: true,
}
}));
const result = await query.run(client);
// { title: string, actors: {name: string}[] }[]
EdgeQL
select Movie {
title,
actors: {
name
}
}
En este punto, puede que se pregunte por qué el segundo argumento e.select
es una función en lugar de un objeto simple. Bueno: el generador de consultas puede hacer mucho más que simples conjuntos de selección de estilo GraphQL. Para empezar, puede definir propiedades calculadas .
Mecanografiado
const query = e.select(e.Movie, (movie) => ({
title: true,
title_upper: e.str_upper(movie.title),
cast_size: e.count(movie.actors)
}));
const result = await query.run(client);
// { title: string; title_upper: string; cast_size: number }[]
EdgeQL
select Movie {
title,
title_upper := str_upper(.title),
cast_size := count(.actors)
}
Nuestra "función de forma" ahora tiene un argumento: movie
. Esta variable representa el “alcance”; podemos usarlo para hacer referencia a las propiedades y enlaces de los usuarios que estamos seleccionando. En este caso, estamos definiendo algunas expresiones calculadas simples usando dos funciones integradas: e.count
y e.str_upper
; el generador de consultas refleja toda la biblioteca estándar de EdgeDB .
Ah, y tenga en cuenta que el generador de consultas infirió correctamente el tipo de title_upper
y cast_size
! El resultado de esta consulta sería algo como esto:
[
{
title: "Iron Man",
title_upper: "IRON MAN",
cast_size: 2
},
// etc.
]
Para agregar una filter
cláusula a su consulta de selección, incluya la filter
clave especial en su forma. Esta clave espera una expresión booleana; lo más común es que esta expresión de voluntad incluya un operador como =
, >=
, ++
, not
y or
; los operadores se expresan con la e.op
función.
A continuación, seleccionamos todas las películas con un título que contiene "matriz" (sin distinción entre mayúsculas y minúsculas).
Mecanografiado
e.select(e.Movie, (movie) => ({
title: true,
release_year: true,
filter: e.op(movie.title, "ilike", "%matrix%"),
}));
EdgeQL
select Movie {
title,
release_year
} filter .title ilike "%matrix%"
Dependiendo de su tipo EdgeDB asociado , las expresiones pueden tener varias propiedades y métodos. Por ejemplo, las expresiones correspondientes a str
valores (como las movie.title
anteriores) se pueden indexar y dividir fácilmente. (Esto también se aplica a las expresiones array
, tuple
y ).json
¡Recuerda que movie.title
no es una cuerda! Es un objeto que representa una consulta que devuelve una cadena. Además, movie.title[0]
devuelve otra expresión más. El generador de consultas implementa esta "indexación mágica" con la ayuda de la API de proxy .
Podemos usar esto para seleccionar todas las películas que comienzan con la letra "A".
Mecanografiado
e.select(e.Movie, (movie) => ({
title: true,
release_year: true,
filter: e.op(movie.title[0], "=", "A"),
}));
EdgeQL
select Movie {
title,
release_year
} filter .title[0] = "A"
En este punto, puede estar pensando que la forma se está amontonando un poco. ¿Por qué usamos un solo objeto para definir nuestra selección de campos, propiedades calculadas y filtros? ¿No habrá conflictos clave?
¡En realidad no! Esta es una decisión muy intencional. EdgeQL reserva ciertas palabras clave como "filtro", por lo que no se puede usar fácilmente como propiedad o nombre de enlace. En cuanto a los campos calculados, no se les permite "sobrescribir" una propiedad/enlace dentro de una forma de selección; TypeScript (y EdgeQL) generará un error.
Con esta API, cada capa de profundidad de consulta corresponde a una única capa de anidamiento de objetos.
e.select(e.Movie, (movie) => ({ id: true, title: true, actors: (actor) => ({ name: true, filter: e.op(actor.name, "ilike", "chris") }), filter: e.op(movie.release_year, "=", 2022) }));
Compare esto con la sintaxis más detallada de los ORM de JavaScript modernos. Prisma requiere dos capas de anidamiento de objetos para cada capa adicional de profundidad de consulta. Esta es la misma consulta expresada con el cliente Prisma.
prisma.movie.findMany({
where: {
release_year: {
eq: 2022
}
},
select: {
id: true,
title: true,
actors: {
select: {
name: true
},
where: {
name: {
contains: "chris",
mode: "insensitive"
}
}
}
}
});
El generador de consultas es lo suficientemente inteligente como para saber cuándo intenta seleccionar un solo objeto. Por ejemplo:
const query = e.select(e.Movie, (movie) => ({
title: true,
filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
}));
const result = await query.run(client);
// { title: string } | null
El tipo de resultado inferido es { title: string } | null
. Si, en cambio, filtra por una propiedad no exclusiva como release_year
, el resultado será una matriz.
const query = e.select(e.Movie, (movie) => ({
title: true,
filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
filter: e.op(movie.release_year, '=', 2022)
}));
const result = await query.run(client);
// { title: string }[]
El generador de consultas detecta cuando filtra una propiedad con una restricción exclusiva (unicidad) (por ejemplo, .id
) con el operador de igualdad ( =
). En estas circunstancias, la consulta solo puede devolver cero o un objeto; esto se refleja en el tipo inferido. Por lo tanto, no hay necesidad de API separadas para .findOne
y .findMany
: el generador de consultas puede resolverlo.
La order_by
tecla especial se puede usar para especificar operaciones de orden en el resultado de select
y limit
/ offset
se puede usar para la paginación.
Mecanografiado
e.select(e.Movie, (movie) => ({
title: true,
order_by: e.count(movie.actors),
limit: 10,
offset: 40
}));
EdgeQL
select Movie {
title
}
order by count(.actors)
limit 10
offset 40
La order_by
llave admite pedidos compuestos con instrucciones personalizables y políticas de manejo de vacíos.
e.select(e.Movie, (movie) => ({
title: true,
order_by: [
{
expression: e.count(movie.actors),
direction: e.ASC,
empty: e.EMPTY_LAST,
},
{
expression: movie.title,
direction: e.DESC,
}
]
}));
Mecanografiado
e.update(e.Movie, (movie) => ({
filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
set: {
title: 'Avengers: Endgame',
},
}));
EdgeQL
e.update(e.Movie, (movie) => ({
filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
set: {
title: 'Avengers: Endgame',
},
}));
El generador de consultas es particularmente útil (o más bien, los ORM son particularmente malos) cuando se establecen propiedades en una versión modificada de su valor actual. Por ejemplo, esta consulta eliminaría los espacios en blanco adicionales de todos los títulos de películas.
Mecanografiado
e.update(e.Movie, (movie) => ({
set: {
title: e.str_trim(movie.title),
},
}));
EdgeQL
update Movie
set {
title := str_trim(.title)
}
Con un ORM, esto es inexpresable; necesitaría escribir un script para iterar a través de todas las películas en su base de datos.
Al actualizar enlaces , el generador de consultas admite una sintaxis especial para agregar o sustraer del conjunto de objetos vinculados.
Mecanografiado
const actors = e.select(e.Person, person => ({
filter: e.op(person.name, 'in', e.set('Benedict Cumberbatch', 'Rachel McAdams'))
}));
const query = e.update(e.Movie, (movie) => ({
filter: e.op(movie.title, '=', "Doctor Strange"),
set: {
actors: {'+=': actors},
}
})).run(client);
EdgeQL
with actors := (
select Person
filter .name in {'Benedict Cumberbatch', 'Rachel McAdams'}
)
update Movie
filter .title = "Doctor Strange"
set {
actors += actors
}
El update
ejemplo anterior también demuestra una de las mayores fortalezas del generador de consultas: la composicionalidad . Debido a que la declaración y ejecución de consultas son dos pasos distintos, es posible declarar partes de una consulta por separado y luego juntarlas todas más tarde. Escribir consultas complejas es como escribir un guión.
Por ejemplo, podemos componer varias expresiones para realizar una inserción + selección anidada en una consulta.
Mecanografiado
const rdj = e.insert(e.Person, {
name: "Robert Downey Jr."
});
const ironMan = e.insert(e.Movie, {
title: "Iron Man",
release_year: 2008,
actors: rdj
});
const query = e.select(ironMan, () => ({
title: true,
release_year: true,
num_actors: e.count(ironMan.actors)
}));
const result = await query.run(client);
// {title: string; release_year: number; num_actors: number}
EdgeQL
with
rdj := (
insert Person {
name := "Robert Downey Jr."
}
),
ironMan := (
insert Movie {
title := "Iron Man",
release_year := 2008
}
)
select ironMan {
title,
release_year,
num_actors := count(.actors)
};
El generador de consultas detecta que newMovie
ocurre varias veces dentro query
y lo extrae en un with
bloque (también conocido como "expresión de tabla común" en lenguaje SQL). Tenga en cuenta que solo hay uno await
. No estamos ejecutando rdj
y ironMan
; son subconsultas que se componen en la "superconsulta" final, que se puede ejecutar en un solo viaje de ida y vuelta a la base de datos.
Como guinda final, el generador de consultas facilita la parametrización de sus consultas. Esto le permite usar datos externos (por ejemplo, el cuerpo de una POST
solicitud entrante) de forma segura.
Mecanografiado
const query = e.params(
{ title: e.str, release_year: e.int64 },
($) => {
return e.insert(e.Movie, {
title: $.title,
release_year: $.release_year,
});
}
);
const result = await query.run(client, {
title: 'Thor: Love and Thunder',
release_year: 2022,
});
EdgeQL
with
title := <str>$title,
release_year := <int64>$release_year
insert Movie {
title := title,
release_year := release_year
}
Para una consulta parametrizada, pasa los parámetros como segundo argumento a .run()
; están fuertemente tipados y validados en tiempo de ejecución.
Esperemos que quede claro a partir de los ejemplos anteriores, pero en términos de poder expresivo, el generador de consultas está más allá de todos los ORM que conocemos. En general, los ORM solo pueden representar operaciones de lectura/escritura relativamente básicas, mientras que un lenguaje de consulta adecuado puede expresar mucho más:
??
en JavaScript)a ? b : c
en JavaScript)union
oin
Esto es por no hablar del modelado de esquemas. EdgeDB admite aún más funciones que faltan en la mayoría de los ORM:
int{16,|32|64}
, float{32|64}
, bigint
y decimal
)duration
fechas y horas no conscientes de la zona horariaEl generador de consultas ha estado disponible desde el lanzamiento de EdgeDB 1.0 en febrero de 2022 y es estable y está listo para la producción. El generador de consultas se actualizó recientemente para admitir todas las características de EdgeDB 2.0, como la e.group
declaración y las variables de esquema global. Esta publicación solo cubre un subconjunto de la funcionalidad completa del generador de consultas; consulte la Documentación para una mirada más completa.
Lanzaremos EdgeDB 2.0 durante un evento de lanzamiento transmitido en vivo el 28 de julio de 2022. Únase a nosotros para algunas charlas relámpago sobre las nuevas funciones más importantes y la primera demostración pública de la nueva interfaz de usuario de administración de EdgeDB.
Fuente: https://www.edgedb.com/blog/designing-the-ultimate-typescript-query-builder
#typescript #EdgeDb
1654588030
TypeScript Deep Dive
I've been looking at the issues that turn up commonly when people start using TypeScript. This is based on the lessons from Stack Overflow / DefinitelyTyped and general engagement with the TypeScript community. You can follow for updates and don't forget to ★ on GitHub 🌹
If you are here to read the book online get started.
Book is completely free so you can copy paste whatever you want without requiring permission. If you have a translation you want me to link here. Send a PR.
You can also download one of the Epub, Mobi, or PDF formats from the actions tab by clicking on the latest build run. You will find the files in the artifacts section.
All the amazing contributors 🌹
Share URL: https://basarat.gitbook.io/typescript/
Author: Basarat
Source Code: https://github.com/basarat/typescript-book/
License: View license
1617257581
¿Quiere restaurar los buzones de correo de PST a Exchange Server? Entonces, estás en la página correcta. Aquí, lo guiaremos sobre cómo puede restaurar fácilmente mensajes y otros elementos de PST a MS Exchange Server.
Muchas veces, los usuarios necesitan restaurar los elementos de datos de PST en Exchange Server, pero debido a la falta de disponibilidad de una solución confiable, los usuarios no pueden obtener la solución. Háganos saber primero sobre el archivo PST y MS Exchange Server.
PST es un formato de archivo utilizado por MS Outlook, un cliente de correo electrónico de Windows y muy popular entre los usuarios domésticos y comerciales.
Por otro lado, Exchange Server es un poderoso servidor de correo electrónico donde todos los datos se almacenan en un archivo EDB. Los usuarios generalmente guardan la copia de seguridad de los buzones de correo de Exchange en el archivo PST, pero muchas veces, los usuarios deben restaurar los datos del archivo PST en Exchange. Para resolver este problema, estamos aquí con una solución profesional que discutiremos en la siguiente sección de esta publicación.
No le recomendamos que elija una solución al azar para restaurar los datos de PST en Exchange Server. Por lo tanto, al realizar varias investigaciones, estamos aquí con una solución inteligente y conveniente, es decir, Exchange Restore Software. Es demasiado fácil de manejar por todos los usuarios y restaurar cómodamente todos los datos del archivo PST a Exchange Server.
El software es demasiado simple de usar y se puede instalar fácilmente en todas las versiones de Windows. Con unos pocos clics, la herramienta puede restaurar los elementos del buzón de Exchange.
No es necesario que MS Outlook restaure los datos PST en Exchange. Todos los correos electrónicos, contactos, notas, calendarios, etc. se restauran desde el archivo PST a Exchange Server.
Todas las versiones de Outlook son compatibles con la herramienta, como Outlook 2019, 2016, 2013, 2010, 2007, etc. La herramienta proporciona varios filtros mediante los cuales se pueden restaurar los datos deseados desde un archivo PST a Exchange Server. El programa se puede instalar en todas las versiones de Windows como Windows 10, 8.1, 8, 7, XP, Vista, etc.
Descargue la versión de demostración del software de restauración de Exchange y analice el funcionamiento del software restaurando los primeros 50 elementos por carpeta.
No existe una solución manual para restaurar los buzones de correo de Exchange desde el archivo PST. Por lo tanto, hemos explicado una solución fácil e inteligente para restaurar datos de archivos PST en Exchange Server. Simplemente puede usar este software y restaurar todos los datos de PST a Exchange Server.
Más información:- https://www.datavare.com/software/exchange-restore.html
#intercambio de software de restauración #intercambio de restauración #buzón del servidor de intercambio #herramienta de restauración de intercambio
1617255938
Si tiene problemas para migrar los buzones de correo de Exchange a Office 365, debe leer este artículo para saber cómo migrar los buzones de correo de Exchange EDB a Office 365. Al migrar a Office 365, los usuarios pueden acceder a sus buzones de correo desde cualquier lugar y desde cualquier dispositivo.
En esta publicación, explicaremos las razones detrás de esta migración y una solución profesional para migrar de Exchange a Office 365.
Office 365 apareció por primera vez en 2011 y, dado que se considera la mejor plataforma para aquellas organizaciones que desean administrar todo su sistema de correo electrónico en la nube. Estas son las características clave de Office 365:
Hay varias formas manuales de migrar los buzones de correo de Exchange EDB a Office 365, pero para evitar estos complicados y prolongados procedimientos, presentamos una solución de terceros, es decir, la herramienta de migración de Exchange, que es automatizada y directa para la migración de Exchange a Office 365. La herramienta funciona rápidamente y migra todos los elementos del buzón de Exchange Server a Office 365.
La herramienta de migración de Datavare Exchange es demasiado fácil de usar y ofrece pasos sencillos para migrar EDB a Office 365:
Por lo tanto, todos sus buzones de correo de Exchange EDB ahora se migran a Office 365.
Nota: puede usar filtros para migrar los elementos de datos deseados de la cuenta de Exchange a la de Office 365
Este blog le indica una solución profesional para la migración de buzones de correo de Exchange a la cuenta de Office 365. Dado que las soluciones manuales son complicadas, sugerimos la herramienta de migración de Exchange, que es demasiado simple de usar. Los usuarios no se enfrentan a problemas al operar el programa. La mejor parte de este software es que no necesita habilidades técnicas para realizar la migración. Se puede comprender el funcionamiento del software descargando la versión de demostración que permite la migración de los primeros 50 elementos por carpeta.
Más información:- https://www.datavare.com/software/edb-migration.html
#herramienta de migración de intercambio #migración de intercambio #migrar buzones de correo de exchange
1659690000
Nós nos propusemos a projetar um construtor de consultas para TypeScript que possa expressar consultas de complexidade arbitrária enquanto infere o tipo de retorno automaticamente.
Não foi fácil, mas com a ajuda de alguns dos recursos modernos mais legais do JavaScript e alguma magia obscura do TypeScript, achamos que conseguimos. O resultado é algo que oferece o melhor de todos os mundos: a segurança de tipo de um ORM juntamente com o poder expressivo de uma linguagem de consulta completa.
Você pode jogar com o construtor de consultas agora! Recomendamos clonar nosso repositório de sandbox do MCU e seguir as instruções para inicializar o projeto - deve levar menos de um minuto.
Nota importante: este é um construtor de consultas para EdgeQL , a linguagem de consulta orientada a objetos do EdgeDB. Ele foi projetado como um sucessor espiritual do SQL e resolve alguns dos maiores problemas de usabilidade. Ao longo desta postagem, clique na guia “EdgeQL” para ver o equivalente EdgeQL de cada expressão do construtor de consultas.
TypeScript
const query = e.select(e.Movie, movie => ({
id: true,
title: true,
actors: {
name: true,
},
filter: e.op(movie.title, '=', 'Iron Man 3')
}));
// inferred type:
// { id: string; title: string; actors: { name: string }[]}
EdgeQL
select Movie {
id,
title,
actors: {
name
}
}
filter .title = "Iron Man 3"
Observe a “forma de seleção” no estilo GraphQL para especificar quais campos buscar. Como seria de esperar, essa consulta retorna um resultado estruturado do tipo JSON, não uma lista simples de linhas.
{
"id": "9278d96e-1932-44e4-a9b3-34e49b592c26",
"title": "Iron Man 3",
"release_year": 2013,
"actors": [
{ "name": "Robert Downey Jr." },
{ "name": "Gwyneth Paltrow" },
{ "name": "Ben Kingsley" }
]
}
A consulta acima assume o seguinte esquema, conforme definido com a linguagem de definição de esquema do EdgeDB .
type Movie {
required property title -> str;
property release_year -> int64;
multi link actors -> Person;
}
type Person {
required property name -> str;
}
Gerando o construtor de consultas
Para começar com o construtor de consultas, você precisará ativar uma instância do EdgeDB. A maneira mais fácil de fazer isso é instalar a CLI do EdgeDB e executá-la edgedb project init
no diretório do seu projeto. Siga o início rápido para uma introdução mais completa.
Em seguida, instale o edgedb
pacote do NPM e execute o seguinte npx
comando.
$ npm install edgedb
$ npx edgeql-js
Este comando examina seu banco de dados e gera um punhado de arquivos no dbschema/edgeql-js
diretório. (Por convenção, dbschema
é o diretório usado para armazenar qualquer coisa relacionada ao EdgeDB, como arquivos de esquema e migração.) Recomendamos importar o construtor de consultas como uma única variável chamada e
.
import e from "./dbschema/edgeql-js";
Essa variável contém tudo o que você precisa para definir consultas EdgeQL arbitrariamente complexas, mas vamos começar pequeno: uma consulta “Hello World”.
import e from "./dbschema/edgeql-js";
const query = e.select("hello world!");
A e.select
função retorna um objeto que representa uma consulta EdgeQL; vamos nos referir a isso como uma “expressão do construtor de consultas” ou simplesmente “expressão”.
Para executar a expressão, passe um cliente para o método da expressão .run()
.
A createClient
função retorna uma instância de Client
: uma classe que gerencia um pool de conexões com sua instância EdgeDB e fornece uma API simples para executar consultas.
import {createClient} from "edgedb";
import e from "./dbschema/edgeql-js";
const client = createClient();
const query = e.select("Hello world!");
const result = await query.run(client);
// => "Hello world!"
O .run
método retorna uma promessa fortemente tipada; o construtor de consultas infere automaticamente o tipo de retorno de todas as expressões. No exemplo acima result
tem um string
tipo. Você pode extrair esse tipo com o $infer
auxiliar.
import {createClient} from "edgedb";
import e, {$infer} from "./dbschema/edgeql-js";
const client = createClient();
const query = e.select("Hello world!");
type query = $infer<typeof query>;
// string
Vamos começar a analisar algumas consultas não triviais.
Use a e.insert
função para escrever insert
consultas.
TypeScript
e.insert(e.Movie, {
title: "Doctor Strange in the Multiverse of Madness",
release_year: 2022
});
EdgeQL
insert Movie {
title := "Doctor Strange in the Multiverse of Madness",
release_year := 2022
}
O primeiro argumento é um tipo de objeto ; elas são geradas automaticamente pelo construtor de consultas. O segundo argumento contém os dados a serem inseridos. Observe que não estamos incluindo uma id
propriedade; que é gerado automaticamente pelo EdgeDB.
Como a title
propriedade tem type str
, e.insert
naturalmente espera um valor de string. Da mesma forma release_year
tem type int64
, então espera um número. A tabela abaixo mapeia cada tipo escalar EdgeDB para seu equivalente TypeScript mais próximo.
Tipo EdgeDB | Tipo de JavaScript |
str | string |
bool | boolean |
float32 float64 int16 int32 int64 | number |
json | string |
uuid | string |
bigint | BigInt |
decimal | N/A (não suportado) |
bytes | Buffer |
datetime | Date |
duration | Duration() |
cal::local_date | LocalDate() |
cal::local_time | LocalTime() |
cal::local_datetime | LocalDateTime() |
Para certos tipos como duration
esse não têm equivalente em JavaScript, implementamos classes personalizadas para representar esse tipo de dados.
Como no EdgeQL, as subconsultas são completamente indolores; para fazer inserções aninhadas, basta soltar uma e.insert
na outra.
TypeScript
e.insert(e.Movie, {
title: "Iron Man",
release_year: 2008,
actors: e.set(
e.insert(e.Person, { name: "Robert Downey Jr." }),
e.insert(e.Person, { name: "Gwyneth Paltrow" })
),
});
EdgeQL
insert Movie {
title := "Iron Man",
release_year : 2008,
actors := {
(insert Person { name := "Robert Downey Jr." }),
(insert Person { name := "Gwyneth Paltrow" })
}
}
Acima, estamos usando a e.set
função para definir um literal definido . No EdgeQL isso é indicado com chaves:select {'a', 'b', 'c'}
Agora sobre a carne e as batatas: selecionando objetos . Vamos começar selecionando todos os filmes no banco de dados.
TypeScript
const query = e.select(e.Movie, () => ({
id: true,
title: true
}));
const result = await query.run(client);
// {id: string; title: string;}[]
EdgeQL
select Movie {
id,
title,
release_year
}
Como um atalho para selecionar todas as propriedades de um objeto, use o operador de propagação em conjunto com a *
propriedade especial. Este é um recurso do construtor de consultas sem equivalente ao EdgeQL (ainda); O EdgeQL simples não oferece suporte à select *
funcionalidade.
const query = e.select(e.Movie, () => ({
...e.Movie['*']
}));
const result = await query.run(client);
/* {
id: string;
title: string;
release_year: number | null;
}[] */
Como seria de esperar, o tipo da release_year
propriedade é number | null
uma propriedade opcional.
As formas podem ser aninhadas para buscar objetos vinculados, como actors
.
TypeScript
const query = e.select(e.Movie, () => ({
title: true,
actors: {
name: true,
}
}));
const result = await query.run(client);
// { title: string, actors: {name: string}[] }[]
EdgeQL
select Movie {
title,
actors: {
name
}
}
Neste ponto, você pode estar se perguntando por que o segundo argumento e.select
é uma função em vez de um objeto simples. Bem: o construtor de consultas pode fazer muito mais do que simples conjuntos de seleção no estilo GraphQL. Para começar, você pode definir propriedades computadas .
TypeScript
const query = e.select(e.Movie, (movie) => ({
title: true,
title_upper: e.str_upper(movie.title),
cast_size: e.count(movie.actors)
}));
const result = await query.run(client);
// { title: string; title_upper: string; cast_size: number }[]
EdgeQL
select Movie {
title,
title_upper := str_upper(.title),
cast_size := count(.actors)
}
Nossa “função shape” agora tem um argumento: movie
. Esta variável representa o “escopo”; podemos usá-lo para referenciar as propriedades e links do(s) usuário(s) que estamos selecionando. Neste caso, estamos definindo algumas expressões computadas simples usando duas funções internas: e.count
e e.str_upper
; o construtor de consultas reflete toda a biblioteca padrão do EdgeDB .
Ah, e observe que o construtor de consultas inferiu corretamente o tipo de title_upper
e cast_size
! O resultado dessa consulta seria algo assim:
[
{
title: "Iron Man",
title_upper: "IRON MAN",
cast_size: 2
},
// etc.
]
Para adicionar uma filter
cláusula à sua consulta de seleção, inclua a filter
chave especial em sua forma. Essa chave espera uma expressão booleana; mais comumente esta expressão irá envolver um operador como =
, >=
, ++
, not
, e or
; operadores são expressos com a e.op
função.
Abaixo, selecionamos todos os filmes com título contendo “matriz” (não diferencia maiúsculas de minúsculas).
TypeScript
e.select(e.Movie, (movie) => ({
title: true,
release_year: true,
filter: e.op(movie.title, "ilike", "%matrix%"),
}));
EdgeQL
select Movie {
title,
release_year
} filter .title ilike "%matrix%"
Dependendo do tipo EdgeDB associado , as expressões podem ter várias propriedades e métodos. Por exemplo, expressões correspondentes a str
valores (como movie.title
acima) podem ser facilmente indexadas e segmentadas. (Isso também é verdade para expressões array
, tuple
, e .)json
Lembre-se que movie.title
não é uma string! É um objeto que representa uma consulta que retorna uma string. Além disso, movie.title[0]
retorna ainda outra expressão. O construtor de consultas implementa essa “indexação mágica” com a ajuda da API Proxy .
Podemos usar isso para selecionar todos os filmes que começam com a letra “A”.
TypeScript
e.select(e.Movie, (movie) => ({
title: true,
release_year: true,
filter: e.op(movie.title[0], "=", "A"),
}));
EdgeQL
select Movie {
title,
release_year
} filter .title[0] = "A"
Neste ponto, você pode estar pensando que a forma está ficando um pouco cheia. Por que estamos usando um único objeto para definir nossa seleção de campo, propriedades computadas e filtros? Não haverá conflitos importantes?
Na verdade não! Esta é uma decisão muito intencional. O EdgeQL reserva certas palavras-chave como “filtro” para que não possa ser facilmente usado como uma propriedade ou nome de link. Quanto aos campos computados, eles não podem “sobrescrever” uma propriedade/link dentro de uma forma de seleção; O TypeScript (e EdgeQL) gerará um erro.
Com essa API, cada camada de profundidade de consulta corresponde a uma única camada de aninhamento de objetos.
e.select(e.Movie, (movie) => ({ id: true, title: true, actors: (actor) => ({ name: true, filter: e.op(actor.name, "ilike", "chris") }), filter: e.op(movie.release_year, "=", 2022) }));
Compare isso com a sintaxe mais detalhada dos ORMs JavaScript modernos. O Prisma requer duas camadas de aninhamento de objetos para cada camada adicional de profundidade de consulta. Aqui está a mesma consulta expressa com o cliente Prisma.
prisma.movie.findMany({
where: {
release_year: {
eq: 2022
}
},
select: {
id: true,
title: true,
actors: {
select: {
name: true
},
where: {
name: {
contains: "chris",
mode: "insensitive"
}
}
}
}
});
O construtor de consultas é inteligente o suficiente para saber quando você está tentando selecionar um único objeto. Por exemplo:
const query = e.select(e.Movie, (movie) => ({
title: true,
filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
}));
const result = await query.run(client);
// { title: string } | null
O tipo de resultado inferido é { title: string } | null
. Se você filtrar em uma propriedade não exclusiva como release_year
, o resultado será uma matriz.
const query = e.select(e.Movie, (movie) => ({
title: true,
filter: e.op(movie.id, '=', e.uuid('2053a8b4-49b1-437a-84c8-e1b0291ccd9f'))
filter: e.op(movie.release_year, '=', 2022)
}));
const result = await query.run(client);
// { title: string }[]
O construtor de consultas detecta quando você filtra uma propriedade com uma restrição exclusiva (exclusividade) (por exemplo, .id
) com o operador de igualdade ( =
). Nessas circunstâncias, a consulta só pode retornar zero ou um objeto; isso se reflete no tipo inferido. Portanto, não há necessidade de APIs separadas para .findOne
e .findMany
— o construtor de consultas pode descobrir.
A order_by
chave especial pode ser usada para especificar operações de ordenação no resultado de select
, e limit
/ offset
pode ser usado para paginação.
TypeScript
e.select(e.Movie, (movie) => ({
title: true,
order_by: e.count(movie.actors),
limit: 10,
offset: 40
}));
EdgeQL
select Movie {
title
}
order by count(.actors)
limit 10
offset 40
A order_by
chave suporta pedidos compostos com instruções personalizáveis e políticas de manipulação de vazios.
e.select(e.Movie, (movie) => ({
title: true,
order_by: [
{
expression: e.count(movie.actors),
direction: e.ASC,
empty: e.EMPTY_LAST,
},
{
expression: movie.title,
direction: e.DESC,
}
]
}));
TypeScript
e.update(e.Movie, (movie) => ({
filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
set: {
title: 'Avengers: Endgame',
},
}));
EdgeQL
e.update(e.Movie, (movie) => ({
filter: e.op(movie.title, '=', 'Avengers: Infinity War - Part II'),
set: {
title: 'Avengers: Endgame',
},
}));
O construtor de consultas é particularmente útil (ou melhor, os ORMs são particularmente ruins) ao definir propriedades para uma versão modificada de seu valor atual. Por exemplo, essa consulta cortaria espaços em branco extras de todos os títulos de filmes.
TypeScript
e.update(e.Movie, (movie) => ({
set: {
title: e.str_trim(movie.title),
},
}));
EdgeQL
update Movie
set {
title := str_trim(.title)
}
Com um ORM, isso é inexprimível; você precisaria escrever um script para percorrer todos os filmes em seu banco de dados.
Ao atualizar links , o construtor de consultas suporta sintaxe especial para anexar ou subtrair do conjunto de objetos vinculados.
TypeScript
const actors = e.select(e.Person, person => ({
filter: e.op(person.name, 'in', e.set('Benedict Cumberbatch', 'Rachel McAdams'))
}));
const query = e.update(e.Movie, (movie) => ({
filter: e.op(movie.title, '=', "Doctor Strange"),
set: {
actors: {'+=': actors},
}
})).run(client);
EdgeQL
with actors := (
select Person
filter .name in {'Benedict Cumberbatch', 'Rachel McAdams'}
)
update Movie
filter .title = "Doctor Strange"
set {
actors += actors
}
O update
exemplo anterior também demonstra um dos maiores pontos fortes do construtor de consultas: composicionalidade . Como a declaração e a execução de consultas são duas etapas distintas, é possível declarar partes de uma consulta separadamente e depois juntá-las. Escrever consultas complexas é como escrever um script.
Por exemplo, podemos compor várias expressões para realizar uma inserção + seleção aninhada em uma consulta.
TypeScript
const rdj = e.insert(e.Person, {
name: "Robert Downey Jr."
});
const ironMan = e.insert(e.Movie, {
title: "Iron Man",
release_year: 2008,
actors: rdj
});
const query = e.select(ironMan, () => ({
title: true,
release_year: true,
num_actors: e.count(ironMan.actors)
}));
const result = await query.run(client);
// {title: string; release_year: number; num_actors: number}
EdgeQL
with
rdj := (
insert Person {
name := "Robert Downey Jr."
}
),
ironMan := (
insert Movie {
title := "Iron Man",
release_year := 2008
}
)
select ironMan {
title,
release_year,
num_actors := count(.actors)
};
O construtor de consultas detecta que newMovie
ocorre várias vezes dentro query
e o extrai em um with
bloco (também conhecido como “expressão de tabela comum” no jargão SQL). Observe que há apenas um await
. Não estamos executando rdj
e ironMan
; são subconsultas que são compostas na “superconsulta” final, que pode ser executada em uma única viagem de ida e volta ao banco de dados.
Como a cereja do bolo final, o construtor de consultas facilita a parametrização de suas consultas. Isso permite que você use dados externos (digamos, o corpo de uma POST
solicitação recebida) de maneira segura.
TypeScript
const query = e.params(
{ title: e.str, release_year: e.int64 },
($) => {
return e.insert(e.Movie, {
title: $.title,
release_year: $.release_year,
});
}
);
const result = await query.run(client, {
title: 'Thor: Love and Thunder',
release_year: 2022,
});
EdgeQL
with
title := <str>$title,
release_year := <int64>$release_year
insert Movie {
title := title,
release_year := release_year
}
Para uma consulta parametrizada, você passa os parâmetros como segundo argumento para .run()
; eles são fortemente tipados e validados em tempo de execução.
Espero que esteja claro nos exemplos acima, mas em termos de poder expressivo, o construtor de consultas está além de todos os ORMs que conhecemos. Em geral, os ORMs podem representar apenas operações de leitura/gravação relativamente básicas, enquanto uma linguagem de consulta adequada pode expressar muito mais:
??
em JavaScript)a ? b : c
em JavaScript)union
ouin
Isso é para não falar da modelagem de esquema. O EdgeDB suporta ainda mais funcionalidades que faltam na maioria dos ORMs:
int{16,|32|64}
, float{32|64}
, bigint
, e decimal
)duration
datas e horas que não reconhecem fuso horárioO construtor de consultas está disponível desde o lançamento do EdgeDB 1.0 em fevereiro de 2022 e está estável e pronto para produção. O construtor de consultas foi atualizado recentemente para suportar todos os recursos do EdgeDB 2.0, como a e.group
instrução e as variáveis de esquema global. Esta postagem abrange apenas um subconjunto da funcionalidade completa do construtor de consultas; consulte a Documentação para uma visão mais abrangente!
Estamos lançando o EdgeDB 2.0 durante um evento de lançamento transmitido ao vivo em 28 de julho de 2022. Junte-se a nós para algumas palestras relâmpago sobre os maiores novos recursos e a primeira demonstração pública da nova interface de administração do EdgeDB.
Fonte: https://www.edgedb.com/blog/designing-the-ultimate-typescript-query-builder