diff --git a/compiler/parser.v b/compiler/parser.v index b716b25401..f17df72e07 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -84,7 +84,7 @@ mut: is_vweb bool is_sql bool sql_i int // $1 $2 $3 - sql_params string // ("select * from users where id = $1", ***"100"***) + sql_params []string // ("select * from users where id = $1", ***"100"***) } const ( @@ -1400,7 +1400,7 @@ fn (p mut Parser) bterm() string { p.gen('$' + p.sql_i.str()) p.cgen.start_cut() p.check_types(p.expression(), typ) - p.sql_params = p.sql_params + p.cgen.cut() + ',' + p.sql_params << p.cgen.cut() //println('sql params = "$p.sql_params"') } else { p.check_types(p.expression(), typ) diff --git a/compiler/query.v b/compiler/query.v index 401df76d39..f09a078e28 100644 --- a/compiler/query.v +++ b/compiler/query.v @@ -6,13 +6,27 @@ module main import strings +fn sql_params2params_gen(sql_params []string, qprefix string) string { + mut params_gen := '' + for i, mparam in sql_params { + param := mparam.trim(` `) + if param[0].is_digit() { + params_gen += '${qprefix}params[$i] = int_str($param).str;\n' + }else{ + sparam := param.trim(`\'`) + params_gen += '${qprefix}params[$i] = "$sparam";\n' + } + } + return params_gen +} + // `db.select from User where id == 1 && nr_bookings > 0` fn (p mut Parser) select_query(fn_ph int) string { // NB: qprefix, p.sql_i, p.sql_params SHOULD be reset for each query, // because we can have many queries in the _same_ scope. qprefix := p.get_tmp().replace('tmp','sql') + '_' p.sql_i = 0 - p.sql_params = '' + p.sql_params = []string mut q := 'select ' p.check(.key_select) @@ -103,11 +117,7 @@ fn (p mut Parser) select_query(fn_ph int) string { } // One object if query_one { - mut params_gen := '' - params := p.sql_params.split(',') - for i, param in params { - params_gen += '${qprefix}params[$i] = int_str($param).str;' - } + mut params_gen := sql_params2params_gen( p.sql_params, qprefix ) p.cgen.insert_before(' char* ${qprefix}params[$p.sql_i]; @@ -131,12 +141,8 @@ ${obj_gen.str()} } // Array else { - q += ' order by id' - mut params_gen := '' - params := p.sql_params.split(',') - for i, param in params { - params_gen += '${qprefix}params[$i] = int_str($param).str;' - } + q += ' order by id' + params_gen := sql_params2params_gen( p.sql_params, qprefix ) p.cgen.insert_before('char* ${qprefix}params[$p.sql_i]; $params_gen @@ -158,8 +164,11 @@ for (int i = 0; i < ${qprefix}rows.len; i++) { } if n == 'count' { return 'int' - } else if query_one { - return 'Option_$table_name' + } else if query_one { + opt_type := 'Option_$table_name' + p.cgen.typedefs << 'typedef Option $opt_type;' + p.table.register_type( opt_type ) + return opt_type } else { p.register_array('array_$table_name') return 'array_$table_name' diff --git a/examples/database/pg/customer.v b/examples/database/pg/customer.v new file mode 100644 index 0000000000..8c520f0a3d --- /dev/null +++ b/examples/database/pg/customer.v @@ -0,0 +1,55 @@ +module main + +import pg + +struct Customer { + id int + name string + nr_orders int + country string +} + +fn main() { + db := pg.connect(pg.Config{host: '127.0.0.1' user: 'myuser' dbname: 'mydb'}) + + nr_customers := db.select count from Customer + println('Total customers: $nr_customers') + + // V syntax can be used to build queries + println('------------------------------------------------------------------------') + bg_customers := db.select from Customer where country = 'Bulgaria' && id != 2 + for customer in bg_customers { + println('$customer.country | $customer.id - $customer.name') + } + + println('------------------------------------------------------------------------') + ru_customers := db.select from Customer where country = 'Russia' + for customer in ru_customers { + println('$customer.country | $customer.id - $customer.name') + } + + // by adding `limit 1` we tell V that there will be only one object + println('------------------------------------------------------------------------') + existing := db.select from Customer where id = 1 limit 1 or { panic(err) } + println('Existing customer name: $existing.name') + println('Existing customer full information:') + println(existing) + + println('------------------------------------------------------------------------') + q := Customer{} + for { + anon := db.select from Customer where id = 12345 && nr_orders > q.nr_orders limit 1 or { eprintln('No such customer. Error: $err') break } + println('Non existing customer name: $anon.name') + break + } + + // TODO: insert a new customer + /* + nc := Customer{ + name: 'John Doe' + nr_orders: 10 + } + db.insert(nc) + */ + +} diff --git a/examples/database/pg/mydb.sql b/examples/database/pg/mydb.sql new file mode 100644 index 0000000000..153b6d5eea --- /dev/null +++ b/examples/database/pg/mydb.sql @@ -0,0 +1,122 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 9.5.19 +-- Dumped by pg_dump version 9.5.19 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: customer; Type: TABLE; Schema: public; Owner: myuser +-- + +CREATE TABLE public.customer ( + id integer NOT NULL, + name text DEFAULT ''::text, + nr_orders integer DEFAULT 0, + country text DEFAULT 'England'::text, + created_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.customer OWNER TO myuser; + +-- +-- Name: customer_id_seq; Type: SEQUENCE; Schema: public; Owner: myuser +-- + +CREATE SEQUENCE public.customer_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.customer_id_seq OWNER TO myuser; + +-- +-- Name: customer_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: myuser +-- + +ALTER SEQUENCE public.customer_id_seq OWNED BY public.customer.id; + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.customer ALTER COLUMN id SET DEFAULT nextval('public.customer_id_seq'::regclass); + + +-- +-- Data for Name: customer; Type: TABLE DATA; Schema: public; Owner: myuser +-- + +COPY public.customer (id, name, nr_orders, country, created_at) FROM stdin; +2 Pippi Långstrump 3 Bulgaria 2019-08-19 09:41:30.78888 +1 Bilbo Begins 11 Bulgaria 2019-08-19 09:40:31.396807 +3 Viktualia Rullgardina 0 Bulgaria 2019-08-19 09:42:52.723223 +4 Krusmynta Efraimsdotter 5 Bulgaria 2019-08-19 09:43:04.083209 +5 Ana Karenina 0 Russia 2019-08-20 15:41:50.244971 +7 Jiji Lolobridgida 0 Italy 2019-08-20 15:42:26.020113 +6 Viktor Savashkin 8 Russia 2019-08-20 15:42:07.213557 +\. + + +-- +-- Name: customer_id_seq; Type: SEQUENCE SET; Schema: public; Owner: myuser +-- + +SELECT pg_catalog.setval('public.customer_id_seq', 1, true); + + +-- +-- Name: customer_pkey; Type: CONSTRAINT; Schema: public; Owner: myuser +-- + +ALTER TABLE ONLY public.customer + ADD CONSTRAINT customer_pkey PRIMARY KEY (id); + + +-- +-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres +-- + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +REVOKE ALL ON SCHEMA public FROM postgres; +GRANT ALL ON SCHEMA public TO postgres; +GRANT ALL ON SCHEMA public TO PUBLIC; + + +-- +-- PostgreSQL database dump complete +-- +