Slide 41 de 53

SQL

Um tipo de linha
type User { id: Int, name: Str }

query findUser(name: Str): User[dyn] {
	SELECT id, name FROM users WHERE name = {name}
}

proc main(): void {
	pooled := hive.sql.pool(hive.sql.DatabaseDriver.SQLite(), "file::memory:?cache=shared", 1, 1)
	if pooled is Result.Ok(db) {
		using db run raw "CREATE TABLE users (id INTEGER, name TEXT)"
		using db run raw "INSERT INTO users (id, name) VALUES (1, 'ada')"

		found := using db run findUser("ada")
		if found is Result.Ok(users) {
			echo users
		}
		hive.sql.close(db)
	} else if pooled is Result.Error(err) {
		echo "não foi possível abrir o banco de dados: {err.message}"
	}
}
Uma coluna, e nenhuma linha
query insertUser(id: Int, name: Str): void {
	INSERT INTO users (id, name) VALUES ({id}, {name})
}

query userNames(): Str[dyn] {
	SELECT name FROM users ORDER BY name
}

query deleteUser(id: Int): void {
	DELETE FROM users WHERE id = {id}
}

proc main(): void {
	opened := hive.sql.pool(hive.sql.DatabaseDriver.SQLite(), "file::memory:?cache=shared", 1, 1)
	if opened is Result.Ok(db) {
		using db run raw "CREATE TABLE users (id INTEGER, name TEXT)"
		using db run insertUser(1, "ada")
		using db run insertUser(2, "grace")

		if using db run userNames() is Result.Ok(names) {
			echo names                      // um Str[dyn]: a coluna em si
		}
		if using db run deleteUser(1) is Result.Ok(rows) {
			echo "apagou {rows} linha"      // um Int: o que a instrução tocou
		}
		hive.sql.close(db)
	}
}
Uma declaração query é um func cujo corpo é SQL inline e cujo tipo de retorno descreve suas linhas. Colunas são associadas a campos pelo nome, não pela posição, então reordenar um SELECT nunca pode remapeá-las silenciosamente, e valores são sempre vinculados como parâmetros — nunca inseridos diretamente no texto — então nada que quem chama fornecer pode mudar o que a instrução significa. connect abre uma única conexão e pool abre um pool inteiro delas, ambos respondendo com um Result<SqlConnection, SqlError>; close libera uma. SQL montado em tempo de execução, em vez de declarado como uma query, passa por run raw em vez disso — sem tipo por construção, e fácil de encontrar com grep.O tipo de retorno faz mais do que nomear a linha: ele decide o formato do resultado inteiro, então dá para ler isso na declaração sem olhar o SQL. Um tipo de linha declarado volta como Result<Row[dyn], SqlError>. Um vetor de um escalar só — Str[dyn], Int[dyn] — não precisa de tipo de linha algum e volta como aquela coluna. E void marca uma instrução em vez de uma consulta, voltando como o número de linhas que ela tocou.Como as colunas são associadas pelo nome, nomeie-as — e o compilador cobra isso. Uma coluna cujo nome difere do campo precisa de um alias (SELECT u.name AS author), um tipo de linha guarda apenas escalares, e SELECT * contra um tipo de linha declarado é um erro de compilação. Ele não diz nem quantas colunas voltam nem como se chamam, então nada consegue alinhá-las aos campos; e o que ele representa muda no dia em que alguém adiciona uma coluna à tabela, o que transformaria uma consulta que compilava e funcionava em linhas que não cabem mais, num programa que ninguém editou. A regra é sobre o resultado e somente sobre o resultado, então count(*), a * 2, uma estrela dentro de uma subconsulta e a lista de seleção de uma instrução void continuam compilando — e SELECT * para uma Table também, que é linhas de células e não tem nomes de campo com que discordar.