├── sql └── .gitignore ├── expected └── .gitignore ├── .gitignore ├── data ├── booktown.dmp └── booktown.sql ├── dump_fdw.control ├── TODO.md ├── dump_fdw--1.0.sql ├── Makefile ├── META.json ├── LICENSE ├── README.md ├── csv_parser.h ├── input └── dump_fdw.source ├── csv_parser.c ├── output └── dump_fdw.source └── dump_fdw.c /sql/.gitignore: -------------------------------------------------------------------------------- 1 | /dump_fdw.sql 2 | -------------------------------------------------------------------------------- /expected/.gitignore: -------------------------------------------------------------------------------- 1 | /dump_fdw.out 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated subdirectories 2 | /log/ 3 | /results/ 4 | /tmp_check/ 5 | -------------------------------------------------------------------------------- /data/booktown.dmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeetMe/dump_fdw/HEAD/data/booktown.dmp -------------------------------------------------------------------------------- /dump_fdw.control: -------------------------------------------------------------------------------- 1 | # dump_fdw extension 2 | comment = 'foreign-data wrapper for pgdump file access' 3 | default_version = '1.0' 4 | module_pathname = '$libdir/dump_fdw' 5 | relocatable = true 6 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Requested Features 2 | ------------------ 3 | 4 | * Add query cost estimation 5 | * Add ANALYZE support 6 | * Reduce memory copies to improve read performance 7 | * Support other compression methods 8 | 9 | 10 | Known Issues 11 | ------------ 12 | 13 | * Fixed-sized buffers lead to memory issues/crashes 14 | * Refactor to get rid of unnecessary parts of initial POC 15 | 16 | -------------------------------------------------------------------------------- /dump_fdw--1.0.sql: -------------------------------------------------------------------------------- 1 | /* contrib/dump_fdw/dump_fdw--1.0.sql */ 2 | 3 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 4 | \echo Use "CREATE EXTENSION dump_fdw" to load this file. \quit 5 | 6 | CREATE FUNCTION dump_fdw_handler() 7 | RETURNS fdw_handler 8 | AS 'MODULE_PATHNAME' 9 | LANGUAGE C STRICT; 10 | 11 | CREATE FUNCTION dump_fdw_validator(text[], oid) 12 | RETURNS void 13 | AS 'MODULE_PATHNAME' 14 | LANGUAGE C STRICT; 15 | 16 | CREATE FOREIGN DATA WRAPPER dump_fdw 17 | HANDLER dump_fdw_handler 18 | VALIDATOR dump_fdw_validator; 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # contrib/dump_fdw/Makefile 2 | 3 | MODULE_big = dump_fdw 4 | 5 | EXTENSION = dump_fdw 6 | DATA = dump_fdw--1.0.sql 7 | 8 | SHLIB_LINK = -lz 9 | OBJS = dump_fdw.o csv_parser.o 10 | REGRESS = dump_fdw 11 | 12 | EXTRA_CLEAN = sql/dump_fdw.sql expected/dump_fdw.out 13 | 14 | ifdef USE_PGXS 15 | PG_CONFIG = pg_config 16 | PGXS := $(shell $(PG_CONFIG) --pgxs) 17 | include $(PGXS) 18 | else 19 | subdir = contrib/dump_fdw 20 | top_builddir = ../.. 21 | include $(top_builddir)/src/Makefile.global 22 | include $(top_srcdir)/contrib/contrib-global.mk 23 | endif 24 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dump_fdw", 3 | "abstract": "foreign-data wrapper for pgdump file access", 4 | "description": "This extension implements a foreign data wrapper for PostgreSQL capable of querying data directly from files in PostgreSQL's custom dump format. This can be used to restore row-specific data.", 5 | "version": "1.0.0", 6 | "release_status": "unstable", 7 | "maintainer": "Jonah H. Harris ", 8 | "license": "bsd", 9 | "provides": { 10 | "dump_fdw": { 11 | "abstract": "foreign-data wrapper for pgdump file access", 12 | "file": "dump_fdw--1.0.sql", 13 | "docfile": "README.md", 14 | "version": "1.0.0" 15 | } 16 | }, 17 | "prereqs": { 18 | "runtime": { 19 | "requires": { 20 | "PostgreSQL": "9.1.0" 21 | } 22 | } 23 | }, 24 | "resources": { 25 | "bugtracker": { 26 | "web": "https://github.com/MeetMe/dump_fdw/issues" 27 | }, 28 | "repository": { 29 | "url": "git://github.com/MeetMe/dump_fdw.git", 30 | "web": "https://github.com/MeetMe/dump_fdw", 31 | "type": "git" 32 | } 33 | }, 34 | "generated_by": "David E. Wheeler", 35 | "meta-spec": { 36 | "version": "1.0.0", 37 | "url": "http://pgxn.org/meta/spec.txt" 38 | }, 39 | "tags": [ 40 | "fdw", 41 | "foreign data wrapper", 42 | "pgdump", 43 | "pg_dump", 44 | "backup", 45 | "custom dump format", 46 | "dump" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, MeetMe, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Postgres Dump Foreign Data Wrapper 2 | ================================== 3 | 4 | This extension implements a foreign data wrapper for PostgreSQL capable of 5 | querying data directly from files in PostgreSQL's custom dump format. This can 6 | be used to restore row-specific data. 7 | 8 | **Note.** This has only been tested with PostgreSQL 9.1/9.2/9.3/9.4 and is 9 | currently a prototype. **DO NOT RUN IT ON A PRODUCTION SERVER**. 10 | 11 | 12 | Introduction 13 | ------------ 14 | 15 | This extension uses the PostgreSQL Custom Dump Format and brings the 16 | following benefits: 17 | 18 | * Analytics - Perform queries directly from your backups. 19 | * Selective Restore - Restore only the rows you need directly from your backups. 20 | 21 | 22 | Building 23 | -------- 24 | 25 | Use the following commands to compile and install dump_fdw: 26 | 27 | # To compile 28 | USE_PGXS=1 make 29 | USE_PGXS=1 make install 30 | 31 | # To run the test 32 | USE_PGXS=1 make installcheck 33 | 34 | 35 | Example 36 | ------- 37 | 38 | As an example, we demonstrate querying data from the BookTown database 39 | (provided by Command Prompt, Inc.): 40 | 41 | Log into Postgres, and run the following commands to install the FDW: 42 | 43 | ```SQL 44 | -- load extension first time after install 45 | CREATE EXTENSION dump_fdw; 46 | 47 | -- create server object 48 | CREATE SERVER dump_server FOREIGN DATA WRAPPER dump_fdw; 49 | ``` 50 | 51 | Next, we create our foreign tables mapped to the pg_dump file: 52 | 53 | ```SQL 54 | -- create foreign table(s) 55 | CREATE FOREIGN TABLE authors ( 56 | id INTEGER, 57 | last_name TEXT, 58 | first_name TEXT 59 | ) SERVER dump_server 60 | OPTIONS ( 61 | file_name '/home/jharris/data/booktown.dmp', 62 | schema_name 'public', 63 | relation_name 'authors' 64 | ) 65 | ; 66 | 67 | CREATE FOREIGN TABLE books ( 68 | id INTEGER, 69 | title TEXT, 70 | author_id INTEGER, 71 | subject_id INTEGER 72 | ) SERVER dump_server 73 | OPTIONS ( 74 | file_name '/home/jharris/data/booktown.dmp', 75 | schema_name 'public', 76 | relation_name 'books' 77 | ) 78 | ; 79 | ``` 80 | 81 | Finally, let's run an example SQL query on the dumped tables: 82 | 83 | ```SQL 84 | -- query the data 85 | SELECT * 86 | FROM books b 87 | JOIN ONLY authors a 88 | ON (a.id = b.author_id) 89 | ORDER BY b.id; 90 | ``` 91 | 92 | Uninstalling dump_fdw 93 | ----------------------- 94 | 95 | Then you can drop the extension: 96 | 97 | postgres=# DROP EXTENSION dump_fdw CASCADE; 98 | 99 | Finally, to uninstall the extension you can run the following command in the 100 | extension's source code directory. This will clean up all the files copied during 101 | the installation: 102 | 103 | $ USE_PGXS=1 make uninstall 104 | 105 | 106 | Copyright 107 | --------- 108 | 109 | Foreign Data Wrapper 110 | 111 | * Copyright (c) 2015 MeetMe, Inc. 112 | 113 | CSV Parser 114 | 115 | * Copyright (C) 2013 Tadas Vilkeliskis 116 | 117 | 118 | License 119 | ------- 120 | 121 | The New BSD License. See LICENSE 122 | 123 | -------------------------------------------------------------------------------- /csv_parser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Tadas Vilkeliskis 3 | * 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | * this software and associated documentation files (the "Software"), to deal in 7 | * the Software without restriction, including without limitation the rights to 8 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | * of the Software, and to permit persons to whom the Software is furnished to do 10 | * so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | #ifndef CSV_PARSER_INCLUDE_GUARD_E95A7C4D_BE4A_49D1_8607_900F36DD25B8 24 | #define CSV_PARSER_INCLUDE_GUARD_E95A7C4D_BE4A_49D1_8607_900F36DD25B8 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | #include 31 | #include 32 | 33 | 34 | typedef enum csv_parser_state_t { 35 | csvps_line_start = 0, 36 | csvps_field_start, 37 | csvps_field_value, 38 | csvps_field_end, 39 | csvps_line_end_begin, 40 | csvps_line_end, 41 | csvps_error, 42 | } csv_parser_state_t; 43 | 44 | 45 | typedef struct csv_parser_t { 46 | csv_parser_state_t state; 47 | /* csv row */ 48 | int row; 49 | /* csv column */ 50 | int col; 51 | uint32_t nread; 52 | /* user data */ 53 | void *data; 54 | } csv_parser_t; 55 | 56 | 57 | /** 58 | * Field callback interface. 59 | * 60 | * Field callback can be called multiple times for the same row and column. 61 | * It's up to the user to construct full field value. 62 | * 63 | * Return 0 value on success, anything else on error. 64 | */ 65 | typedef int (*csv_parser_field_calback_t)(csv_parser_t *parser, 66 | const char *data, 67 | size_t length, 68 | int row, 69 | int col); 70 | 71 | 72 | typedef struct csv_parser_settings_t { 73 | char delimiter; 74 | csv_parser_field_calback_t field_cb; 75 | } csv_parser_settings_t; 76 | 77 | 78 | void csv_parser_init(csv_parser_t *parser); 79 | 80 | /** 81 | * Executes CSV parser on the given data. Returns number 82 | * of bytes read. User should always check if parser state 83 | * is ``csvps_error``. Parser will get into the error state 84 | * if field callback returns a non-zero value. 85 | */ 86 | size_t csv_parser_execute(csv_parser_t *parser, 87 | const csv_parser_settings_t *settings, 88 | const char *data, 89 | size_t data_len); 90 | 91 | 92 | #ifdef __cplusplus 93 | } 94 | #endif 95 | #endif /* end of include guard */ 96 | -------------------------------------------------------------------------------- /input/dump_fdw.source: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | SET client_min_messages TO 'error'; 3 | DROP ROLE IF EXISTS dump_fdw_superuser; 4 | RESET client_min_messages; 5 | CREATE ROLE dump_fdw_superuser LOGIN SUPERUSER; 6 | CREATE EXTENSION dump_fdw; 7 | SET ROLE dump_fdw_superuser; 8 | CREATE SERVER dump_server FOREIGN DATA WRAPPER dump_fdw; 9 | 10 | CREATE FOREIGN TABLE alternate_stock ( 11 | isbn text, 12 | cost numeric(5,2), 13 | retail numeric(5,2), 14 | stock integer 15 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'alternate_stock'); 16 | 17 | CREATE FOREIGN TABLE authors ( 18 | id integer, 19 | last_name text, 20 | first_name text 21 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'authors'); 22 | 23 | CREATE FOREIGN TABLE book_backup ( 24 | id integer, 25 | title text, 26 | author_id integer, 27 | subject_id integer 28 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'book_backup'); 29 | 30 | CREATE FOREIGN TABLE book_queue ( 31 | title text, 32 | author_id integer, 33 | subject_id integer, 34 | approved boolean 35 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'book_queue'); 36 | 37 | CREATE FOREIGN TABLE books ( 38 | id integer, 39 | title text, 40 | author_id integer, 41 | subject_id integer 42 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'books'); 43 | 44 | CREATE FOREIGN TABLE customers ( 45 | id integer, 46 | last_name text, 47 | first_name text 48 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'customers'); 49 | 50 | CREATE FOREIGN TABLE daily_inventory ( 51 | isbn text, 52 | is_stocked boolean 53 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'daily_inventory'); 54 | 55 | CREATE FOREIGN TABLE distinguished_authors ( 56 | id integer, 57 | last_name text, 58 | first_name text, 59 | award text 60 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'distinguished_authors'); 61 | 62 | CREATE FOREIGN TABLE editions ( 63 | isbn text, 64 | book_id integer, 65 | edition integer, 66 | publisher_id integer, 67 | publication date, 68 | type character(1) 69 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'editions'); 70 | 71 | CREATE FOREIGN TABLE employees ( 72 | id integer, 73 | last_name text, 74 | first_name text 75 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'employees'); 76 | 77 | CREATE FOREIGN TABLE favorite_authors ( 78 | employee_id integer, 79 | authors_and_titles text[] 80 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'favorite_authors'); 81 | 82 | CREATE FOREIGN TABLE favorite_books ( 83 | employee_id integer, 84 | books text[] 85 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'favorite_books'); 86 | 87 | CREATE FOREIGN TABLE money_example ( 88 | money_cash money, 89 | numeric_cash numeric(6,2) 90 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'money_example'); 91 | 92 | CREATE FOREIGN TABLE my_list ( 93 | todos text 94 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'my_list'); 95 | 96 | CREATE FOREIGN TABLE numeric_values ( 97 | num numeric(30,6) 98 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'numeric_values'); 99 | 100 | CREATE FOREIGN TABLE publishers ( 101 | id integer, 102 | name text, 103 | address text 104 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'publishers'); 105 | 106 | CREATE FOREIGN TABLE schedules ( 107 | employee_id integer, 108 | schedule text 109 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'schedules'); 110 | 111 | CREATE FOREIGN TABLE shipments ( 112 | id integer, 113 | customer_id integer, 114 | isbn text, 115 | ship_date timestamp with time zone 116 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'shipments'); 117 | 118 | CREATE FOREIGN TABLE states ( 119 | id integer, 120 | name text, 121 | abbreviation character(2) 122 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'states'); 123 | 124 | CREATE FOREIGN TABLE stock ( 125 | isbn text, 126 | cost numeric(5,2), 127 | retail numeric(5,2), 128 | stock integer 129 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'stock'); 130 | 131 | CREATE FOREIGN TABLE stock_backup ( 132 | isbn text, 133 | cost numeric(5,2), 134 | retail numeric(5,2), 135 | stock integer 136 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'stock_backup'); 137 | 138 | CREATE FOREIGN TABLE subjects ( 139 | id integer, 140 | subject text, 141 | location text 142 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'subjects'); 143 | 144 | CREATE FOREIGN TABLE text_sorting ( 145 | letter character(1) 146 | ) SERVER dump_server OPTIONS (file_name '@abs_srcdir@/data/booktown.dmp', schema_name 'public', relation_name 'text_sorting'); 147 | 148 | SELECT * 149 | FROM books b 150 | JOIN ONLY authors a 151 | ON (a.id = b.author_id) 152 | ORDER BY b.id; 153 | 154 | -- cleanup 155 | RESET ROLE; 156 | DROP EXTENSION dump_fdw CASCADE; 157 | DROP ROLE dump_fdw_superuser; 158 | COMMIT; 159 | 160 | -------------------------------------------------------------------------------- /csv_parser.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Tadas Vilkeliskis 3 | * 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | * this software and associated documentation files (the "Software"), to deal in 7 | * the Software without restriction, including without limitation the rights to 8 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | * of the Software, and to permit persons to whom the Software is furnished to do 10 | * so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | void csv_parser_init(csv_parser_t *parser) 29 | { 30 | assert(parser); 31 | parser->state = csvps_line_start; 32 | parser->row = -1; 33 | parser->col = -1; 34 | parser->nread = 0; 35 | parser->data = NULL; 36 | } 37 | 38 | 39 | size_t csv_parser_execute(csv_parser_t *parser, 40 | const csv_parser_settings_t *settings, 41 | const char *data, 42 | size_t data_len) 43 | { 44 | assert(parser); 45 | assert(settings); 46 | assert(data); 47 | 48 | const char *cursor = data; 49 | const char *field_value = NULL; 50 | const char *data_end = data + data_len; 51 | int r; 52 | parser->nread = 0; 53 | 54 | if (data_len < 1) { 55 | return 0; 56 | } 57 | 58 | while (cursor < data_end) { 59 | char ch = *cursor; 60 | switch (parser->state) { 61 | case csvps_line_start: 62 | field_value = NULL; 63 | parser->row += 1; 64 | parser->col = -1; 65 | if (ch == '\r' || ch == '\n') { 66 | parser->state = csvps_line_end_begin; 67 | } else { 68 | parser->state = csvps_field_start; 69 | } 70 | break; 71 | case csvps_field_start: 72 | parser->col += 1; 73 | parser->state = csvps_field_value; 74 | field_value = cursor; 75 | break; 76 | case csvps_field_value: 77 | if (ch == settings->delimiter) { 78 | parser->state = csvps_field_end; 79 | } else if (ch == '\r' || ch == '\n') { 80 | parser->state = csvps_line_end_begin; 81 | } else { 82 | // If we previously been in csvps_field_value state 83 | // and field_value is not set, set it right away. 84 | // This can happen when we execute parser multiple 85 | // times in the value state. 86 | if (field_value == NULL) { 87 | field_value = cursor; 88 | } 89 | 90 | cursor++; 91 | if (cursor == data_end) { 92 | if (settings->field_cb && field_value) { 93 | r = settings->field_cb(parser, 94 | field_value, 95 | cursor - field_value, 96 | parser->row, 97 | parser->col); 98 | if (r) { 99 | parser->state = csvps_error; 100 | return parser->nread; 101 | } 102 | } 103 | } 104 | parser->nread++; 105 | } 106 | break; 107 | case csvps_field_end: 108 | // callback 109 | if (settings->field_cb && field_value) { 110 | r = settings->field_cb(parser, 111 | field_value, 112 | cursor - field_value, 113 | parser->row, 114 | parser->col); 115 | if (r) { 116 | parser->state = csvps_error; 117 | return parser->nread; 118 | } 119 | } 120 | 121 | parser->state = csvps_field_start; 122 | cursor++; 123 | parser->nread++; 124 | break; 125 | case csvps_line_end_begin: 126 | // callback 127 | if (settings->field_cb && field_value) { 128 | r = settings->field_cb(parser, 129 | field_value, 130 | cursor - field_value, 131 | parser->row, 132 | parser->col); 133 | if (r) { 134 | parser->state = csvps_error; 135 | return parser->nread; 136 | } 137 | } 138 | parser->state = csvps_line_end; 139 | break; 140 | case csvps_line_end: 141 | if (ch == '\r' || ch == '\n') { 142 | cursor++; 143 | parser->nread++; 144 | } else { 145 | parser->state = csvps_line_start; 146 | } 147 | break; 148 | case csvps_error: 149 | return parser->nread; 150 | break; 151 | default: 152 | assert(0 && "invalid parser state"); 153 | break; 154 | } 155 | } 156 | 157 | return parser->nread; 158 | } 159 | -------------------------------------------------------------------------------- /output/dump_fdw.source: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | SET client_min_messages TO 'error'; 3 | DROP ROLE IF EXISTS dump_fdw_superuser; 4 | RESET client_min_messages; 5 | CREATE ROLE dump_fdw_superuser LOGIN SUPERUSER; 6 | CREATE EXTENSION dump_fdw; 7 | SET ROLE dump_fdw_superuser; 8 | CREATE SERVER dump_server FOREIGN DATA WRAPPER dump_fdw; 9 | CREATE FOREIGN TABLE alternate_stock ( 10 | isbn text, 11 | cost numeric(5,2), 12 | retail numeric(5,2), 13 | stock integer 14 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'alternate_stock'); 15 | CREATE FOREIGN TABLE authors ( 16 | id integer, 17 | last_name text, 18 | first_name text 19 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'authors'); 20 | CREATE FOREIGN TABLE book_backup ( 21 | id integer, 22 | title text, 23 | author_id integer, 24 | subject_id integer 25 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'book_backup'); 26 | CREATE FOREIGN TABLE book_queue ( 27 | title text, 28 | author_id integer, 29 | subject_id integer, 30 | approved boolean 31 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'book_queue'); 32 | CREATE FOREIGN TABLE books ( 33 | id integer, 34 | title text, 35 | author_id integer, 36 | subject_id integer 37 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'books'); 38 | CREATE FOREIGN TABLE customers ( 39 | id integer, 40 | last_name text, 41 | first_name text 42 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'customers'); 43 | CREATE FOREIGN TABLE daily_inventory ( 44 | isbn text, 45 | is_stocked boolean 46 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'daily_inventory'); 47 | CREATE FOREIGN TABLE distinguished_authors ( 48 | id integer, 49 | last_name text, 50 | first_name text, 51 | award text 52 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'distinguished_authors'); 53 | CREATE FOREIGN TABLE editions ( 54 | isbn text, 55 | book_id integer, 56 | edition integer, 57 | publisher_id integer, 58 | publication date, 59 | type character(1) 60 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'editions'); 61 | CREATE FOREIGN TABLE employees ( 62 | id integer, 63 | last_name text, 64 | first_name text 65 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'employees'); 66 | CREATE FOREIGN TABLE favorite_authors ( 67 | employee_id integer, 68 | authors_and_titles text[] 69 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'favorite_authors'); 70 | CREATE FOREIGN TABLE favorite_books ( 71 | employee_id integer, 72 | books text[] 73 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'favorite_books'); 74 | CREATE FOREIGN TABLE money_example ( 75 | money_cash money, 76 | numeric_cash numeric(6,2) 77 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'money_example'); 78 | CREATE FOREIGN TABLE my_list ( 79 | todos text 80 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'my_list'); 81 | CREATE FOREIGN TABLE numeric_values ( 82 | num numeric(30,6) 83 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'numeric_values'); 84 | CREATE FOREIGN TABLE publishers ( 85 | id integer, 86 | name text, 87 | address text 88 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'publishers'); 89 | CREATE FOREIGN TABLE schedules ( 90 | employee_id integer, 91 | schedule text 92 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'schedules'); 93 | CREATE FOREIGN TABLE shipments ( 94 | id integer, 95 | customer_id integer, 96 | isbn text, 97 | ship_date timestamp with time zone 98 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'shipments'); 99 | CREATE FOREIGN TABLE states ( 100 | id integer, 101 | name text, 102 | abbreviation character(2) 103 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'states'); 104 | CREATE FOREIGN TABLE stock ( 105 | isbn text, 106 | cost numeric(5,2), 107 | retail numeric(5,2), 108 | stock integer 109 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'stock'); 110 | CREATE FOREIGN TABLE stock_backup ( 111 | isbn text, 112 | cost numeric(5,2), 113 | retail numeric(5,2), 114 | stock integer 115 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'stock_backup'); 116 | CREATE FOREIGN TABLE subjects ( 117 | id integer, 118 | subject text, 119 | location text 120 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'subjects'); 121 | CREATE FOREIGN TABLE text_sorting ( 122 | letter character(1) 123 | ) SERVER dump_server OPTIONS (file_name '/Users/jharris/Projects/pgdump/dump_fdw/data/booktown.dmp', schema_name 'public', relation_name 'text_sorting'); 124 | SELECT * 125 | FROM books b 126 | JOIN ONLY authors a 127 | ON (a.id = b.author_id) 128 | ORDER BY b.id; 129 | id | title | author_id | subject_id | id | last_name | first_name 130 | -------+-----------------------+-----------+------------+-------+--------------+------------------ 131 | 156 | The Tell-Tale Heart | 115 | 9 | 115 | Poe | Edgar Allen 132 | 190 | Little Women | 16 | 6 | 16 | Alcott | Louisa May 133 | 1234 | The Velveteen Rabbit | 25041 | 3 | 25041 | Bianco | Margery Williams 134 | 1501 | Goodnight Moon | 2031 | 2 | 2031 | Brown | Margaret Wise 135 | 2038 | Dynamic Anatomy | 1644 | 0 | 1644 | Hogarth | Burne 136 | 4267 | 2001: A Space Odyssey | 2001 | 15 | 2001 | Clarke | Arthur C. 137 | 4513 | Dune | 1866 | 15 | 1866 | Herbert | Frank 138 | 7808 | The Shining | 4156 | 9 | 4156 | King | Stephen 139 | 25908 | Franklin in the Dark | 15990 | 2 | 15990 | Bourgeois | Paulette 140 | 41472 | Practical PostgreSQL | 1212 | 4 | 1212 | Worsley | John 141 | 41473 | Programming Python | 7805 | 4 | 7805 | Lutz | Mark 142 | 41477 | Learning Python | 7805 | 4 | 7805 | Lutz | Mark 143 | 41478 | Perl Cookbook | 7806 | 4 | 7806 | Christiansen | Tom 144 | (13 rows) 145 | 146 | -- cleanup 147 | RESET ROLE; 148 | DROP EXTENSION dump_fdw CASCADE; 149 | NOTICE: drop cascades to 24 other objects 150 | DETAIL: drop cascades to server dump_server 151 | drop cascades to foreign table alternate_stock 152 | drop cascades to foreign table authors 153 | drop cascades to foreign table book_backup 154 | drop cascades to foreign table book_queue 155 | drop cascades to foreign table books 156 | drop cascades to foreign table customers 157 | drop cascades to foreign table daily_inventory 158 | drop cascades to foreign table distinguished_authors 159 | drop cascades to foreign table editions 160 | drop cascades to foreign table employees 161 | drop cascades to foreign table favorite_authors 162 | drop cascades to foreign table favorite_books 163 | drop cascades to foreign table money_example 164 | drop cascades to foreign table my_list 165 | drop cascades to foreign table numeric_values 166 | drop cascades to foreign table publishers 167 | drop cascades to foreign table schedules 168 | drop cascades to foreign table shipments 169 | drop cascades to foreign table states 170 | drop cascades to foreign table stock 171 | drop cascades to foreign table stock_backup 172 | drop cascades to foreign table subjects 173 | drop cascades to foreign table text_sorting 174 | DROP ROLE dump_fdw_superuser; 175 | COMMIT; 176 | -------------------------------------------------------------------------------- /data/booktown.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Selected TOC Entries: 3 | -- 4 | -- 5 | -- TOC Entry ID 1 (OID 0) 6 | -- 7 | -- Name: booktown Type: DATABASE Owner: postgres 8 | -- 9 | 10 | Create Database "booktown"; 11 | 12 | \connect booktown jharris 13 | 14 | BEGIN; 15 | 16 | -- 17 | -- TOC Entry ID 2 (OID 2991542) 18 | -- 19 | -- Name: DATABASE "booktown" Type: COMMENT Owner: 20 | -- 21 | 22 | COMMENT ON DATABASE "booktown" IS 'The Book Town Database.'; 23 | 24 | -- 25 | -- TOC Entry ID 33 (OID 3629264) 26 | -- 27 | -- Name: books Type: TABLE Owner: manager 28 | -- 29 | 30 | CREATE TABLE "books" ( 31 | "id" integer NOT NULL, 32 | "title" text NOT NULL, 33 | "author_id" integer, 34 | "subject_id" integer, 35 | Constraint "books_id_pkey" Primary Key ("id") 36 | ); 37 | 38 | CREATE TABLE "publishers" ( 39 | "id" integer NOT NULL, 40 | "name" text, 41 | "address" text, 42 | Constraint "publishers_pkey" Primary Key ("id") 43 | ); 44 | 45 | CREATE TABLE "authors" ( 46 | "id" integer NOT NULL, 47 | "last_name" text, 48 | "first_name" text, 49 | Constraint "authors_pkey" Primary Key ("id") 50 | ); 51 | 52 | CREATE TABLE "numeric_values" ( 53 | "num" numeric(30,6) 54 | ); 55 | 56 | -- 57 | -- TOC Entry ID 20 (OID 3390866) 58 | -- 59 | -- Name: daily_inventory Type: TABLE Owner: postgres 60 | -- 61 | 62 | CREATE TABLE "daily_inventory" ( 63 | "isbn" text, 64 | "is_stocked" boolean 65 | ); 66 | 67 | -- 68 | -- TOC Entry ID 21 (OID 3391084) 69 | -- 70 | -- Name: money_example Type: TABLE Owner: postgres 71 | -- 72 | 73 | CREATE TABLE "money_example" ( 74 | "money_cash" money, 75 | "numeric_cash" numeric(6,2) 76 | ); 77 | 78 | -- 79 | -- TOC Entry ID 22 (OID 3391184) 80 | -- 81 | -- Name: shipments Type: TABLE Owner: postgres 82 | -- 83 | 84 | CREATE TABLE "shipments" ( 85 | "id" integer DEFAULT nextval('"shipments_ship_id_seq"'::text) NOT NULL, 86 | "customer_id" integer, 87 | "isbn" text, 88 | "ship_date" timestamp with time zone 89 | ); 90 | 91 | -- 92 | -- TOC Entry ID 24 (OID 3391454) 93 | -- 94 | -- Name: customers Type: TABLE Owner: manager 95 | -- 96 | 97 | CREATE TABLE "customers" ( 98 | "id" integer NOT NULL, 99 | "last_name" text, 100 | "first_name" text, 101 | Constraint "customers_pkey" Primary Key ("id") 102 | ); 103 | 104 | -- 105 | -- TOC Entry ID 15 (OID 3389632) 106 | -- 107 | -- Name: states Type: TABLE Owner: postgres 108 | -- 109 | 110 | CREATE TABLE "states" ( 111 | "id" integer NOT NULL, 112 | "name" text, 113 | "abbreviation" character(2), 114 | Constraint "state_pkey" Primary Key ("id") 115 | ); 116 | 117 | -- 118 | -- TOC Entry ID 16 (OID 3389702) 119 | -- 120 | -- Name: my_list Type: TABLE Owner: postgres 121 | -- 122 | 123 | CREATE TABLE "my_list" ( 124 | "todos" text 125 | ); 126 | 127 | -- 128 | -- TOC Entry ID 17 (OID 3390348) 129 | -- 130 | -- Name: stock Type: TABLE Owner: postgres 131 | -- 132 | 133 | CREATE TABLE "stock" ( 134 | "isbn" text NOT NULL, 135 | "cost" numeric(5,2), 136 | "retail" numeric(5,2), 137 | "stock" integer, 138 | Constraint "stock_pkey" Primary Key ("isbn") 139 | ); 140 | 141 | CREATE TABLE "book_queue" ( 142 | "title" text NOT NULL, 143 | "author_id" integer, 144 | "subject_id" integer, 145 | "approved" boolean 146 | ); 147 | 148 | CREATE TABLE "stock_backup" ( 149 | "isbn" text, 150 | "cost" numeric(5,2), 151 | "retail" numeric(5,2), 152 | "stock" integer 153 | ); 154 | 155 | 156 | CREATE TABLE "favorite_books" ( 157 | "employee_id" integer, 158 | "books" text[] 159 | ); 160 | 161 | CREATE TABLE "employees" ( 162 | "id" integer NOT NULL, 163 | "last_name" text NOT NULL, 164 | "first_name" text, 165 | CONSTRAINT "employees_id" CHECK ((id > 100)), 166 | Constraint "employees_pkey" Primary Key ("id") 167 | ); 168 | 169 | CREATE TABLE "editions" ( 170 | "isbn" text NOT NULL, 171 | "book_id" integer, 172 | "edition" integer, 173 | "publisher_id" integer, 174 | "publication" date, 175 | "type" character(1), 176 | CONSTRAINT "integrity" CHECK (((book_id NOTNULL) AND (edition NOTNULL))), 177 | Constraint "pkey" Primary Key ("isbn") 178 | ); 179 | 180 | CREATE TABLE "distinguished_authors" ( 181 | "award" text 182 | ) 183 | INHERITS ("authors"); 184 | 185 | CREATE TABLE "favorite_authors" ( 186 | "employee_id" integer, 187 | "authors_and_titles" text[] 188 | ); 189 | 190 | CREATE TABLE "text_sorting" ( 191 | "letter" character(1) 192 | ); 193 | 194 | 195 | CREATE TABLE "subjects" ( 196 | "id" integer NOT NULL, 197 | "subject" text, 198 | "location" text, 199 | Constraint "subjects_pkey" Primary Key ("id") 200 | ); 201 | 202 | CREATE TABLE "alternate_stock" ( 203 | "isbn" text, 204 | "cost" numeric(5,2), 205 | "retail" numeric(5,2), 206 | "stock" integer 207 | ); 208 | 209 | -- 210 | -- TOC Entry ID 40 (OID 3752020) 211 | -- 212 | -- Name: book_backup Type: TABLE Owner: postgres 213 | -- 214 | 215 | CREATE TABLE "book_backup" ( 216 | "id" integer, 217 | "title" text, 218 | "author_id" integer, 219 | "subject_id" integer 220 | ); 221 | 222 | CREATE TABLE "schedules" ( 223 | "employee_id" integer NOT NULL, 224 | "schedule" text, 225 | Constraint "schedules_pkey" Primary Key ("employee_id") 226 | ); 227 | 228 | 229 | COPY "publishers" FROM stdin; 230 | 150 Kids Can Press Kids Can Press, 29 Birch Ave. Toronto,ONM4V 1E2 231 | 91 Henry Holt & Company, Inc. Henry Holt & Company, Inc. 115 West 18th Street New York, NY 10011 232 | 113 O'Reilly & Associates O'Reilly & Associates, Inc. 101 Morris St, Sebastopol, CA 95472 233 | 62 Watson-Guptill Publications 1515 Boradway, New York, NY 10036 234 | 105 Noonday Press Farrar Straus & Giroux Inc, 19 Union Square W, New York, NY 10003 235 | 99 Ace Books The Berkley Publishing Group, Penguin Putnam Inc, 375 Hudson St, New York, NY 10014 236 | 101 Roc Penguin Putnam Inc, 375 Hudson St, New York, NY 10014 237 | 163 Mojo Press Mojo Press, PO Box 1215, Dripping Springs, TX 78720 238 | 171 Books of Wonder Books of Wonder, 16 W. 18th St. New York, NY, 10011 239 | 102 Penguin Penguin Putnam Inc, 375 Hudson St, New York, NY 10014 240 | 75 Doubleday Random House, Inc, 1540 Broadway, New York, NY 10036 241 | 65 HarperCollins HarperCollins Publishers, 10 E 53rd St, New York, NY 10022 242 | 59 Random House Random House, Inc, 1540 Broadway, New York, NY 10036 243 | \. 244 | -- 245 | -- Data for TOC Entry ID 113 (OID 3389594) 246 | -- 247 | -- Name: authors Type: TABLE DATA Owner: manager 248 | -- 249 | 250 | 251 | COPY "authors" FROM stdin; 252 | 1111 Denham Ariel 253 | 1212 Worsley John 254 | 15990 Bourgeois Paulette 255 | 25041 Bianco Margery Williams 256 | 16 Alcott Louisa May 257 | 4156 King Stephen 258 | 1866 Herbert Frank 259 | 1644 Hogarth Burne 260 | 2031 Brown Margaret Wise 261 | 115 Poe Edgar Allen 262 | 7805 Lutz Mark 263 | 7806 Christiansen Tom 264 | 1533 Brautigan Richard 265 | 1717 Brite Poppy Z. 266 | 2112 Gorey Edward 267 | 2001 Clarke Arthur C. 268 | 1213 Brookins Andrew 269 | \. 270 | -- 271 | -- Data for TOC Entry ID 114 (OID 3389632) 272 | -- 273 | -- Name: states Type: TABLE DATA Owner: postgres 274 | -- 275 | 276 | 277 | COPY "states" FROM stdin; 278 | 42 Washington WA 279 | 51 Oregon OR 280 | \. 281 | -- 282 | -- Data for TOC Entry ID 115 (OID 3389702) 283 | -- 284 | -- Name: my_list Type: TABLE DATA Owner: postgres 285 | -- 286 | 287 | 288 | COPY "my_list" FROM stdin; 289 | Pick up laundry. 290 | Send out bills. 291 | Wrap up Grand Unifying Theory for publication. 292 | \. 293 | -- 294 | -- Data for TOC Entry ID 116 (OID 3390348) 295 | -- 296 | -- Name: stock Type: TABLE DATA Owner: postgres 297 | -- 298 | 299 | 300 | COPY "stock" FROM stdin; 301 | 0385121679 29.00 36.95 65 302 | 039480001X 30.00 32.95 31 303 | 0394900014 23.00 23.95 0 304 | 044100590X 36.00 45.95 89 305 | 0441172717 17.00 21.95 77 306 | 0451160916 24.00 28.95 22 307 | 0451198492 36.00 46.95 0 308 | 0451457994 17.00 22.95 0 309 | 0590445065 23.00 23.95 10 310 | 0679803335 20.00 24.95 18 311 | 0694003611 25.00 28.95 50 312 | 0760720002 18.00 23.95 28 313 | 0823015505 26.00 28.95 16 314 | 0929605942 19.00 21.95 25 315 | 1885418035 23.00 24.95 77 316 | 0394800753 16.00 16.95 4 317 | \. 318 | -- 319 | -- Data for TOC Entry ID 117 (OID 3390653) 320 | -- 321 | -- Name: numeric_values Type: TABLE DATA Owner: postgres 322 | -- 323 | 324 | 325 | COPY "numeric_values" FROM stdin; 326 | 68719476736.000000 327 | 68719476737.000000 328 | 6871947673778.000000 329 | 999999999999999999999999.999900 330 | 999999999999999999999999.999999 331 | -999999999999999999999999.999999 332 | -100000000000000000000000.999999 333 | 1.999999 334 | 2.000000 335 | 2.000000 336 | 999999999999999999999999.999999 337 | 999999999999999999999999.000000 338 | \. 339 | -- 340 | -- Data for TOC Entry ID 118 (OID 3390866) 341 | -- 342 | -- Name: daily_inventory Type: TABLE DATA Owner: postgres 343 | -- 344 | 345 | 346 | COPY "daily_inventory" FROM stdin; 347 | 039480001X t 348 | 044100590X t 349 | 0451198492 f 350 | 0394900014 f 351 | 0441172717 t 352 | 0451160916 f 353 | 0385121679 \N 354 | \. 355 | -- 356 | -- Data for TOC Entry ID 119 (OID 3391084) 357 | -- 358 | -- Name: money_example Type: TABLE DATA Owner: postgres 359 | -- 360 | 361 | 362 | COPY "money_example" FROM stdin; 363 | $12.24 12.24 364 | \. 365 | -- 366 | -- Data for TOC Entry ID 120 (OID 3391184) 367 | -- 368 | -- Name: shipments Type: TABLE DATA Owner: postgres 369 | -- 370 | 371 | 372 | COPY "shipments" FROM stdin; 373 | 375 142 039480001X 2001-08-06 09:29:21-07 374 | 323 671 0451160916 2001-08-14 10:36:41-07 375 | 998 1045 0590445065 2001-08-12 12:09:47-07 376 | 749 172 0694003611 2001-08-11 10:52:34-07 377 | 662 655 0679803335 2001-08-09 07:30:07-07 378 | 806 1125 0760720002 2001-08-05 09:34:04-07 379 | 102 146 0394900014 2001-08-11 13:34:08-07 380 | 813 112 0385121679 2001-08-08 09:53:46-07 381 | 652 724 1885418035 2001-08-14 13:41:39-07 382 | 599 430 0929605942 2001-08-10 08:29:42-07 383 | 969 488 0441172717 2001-08-14 08:42:58-07 384 | 433 898 044100590X 2001-08-12 08:46:35-07 385 | 660 409 0451457994 2001-08-07 11:56:42-07 386 | 310 738 0451198492 2001-08-15 14:02:01-07 387 | 510 860 0823015505 2001-08-14 07:33:47-07 388 | 997 185 039480001X 2001-08-10 13:47:52-07 389 | 999 221 0451160916 2001-08-14 13:45:51-07 390 | 56 880 0590445065 2001-08-14 13:49:00-07 391 | 72 574 0694003611 2001-08-06 07:49:44-07 392 | 146 270 039480001X 2001-08-13 09:42:10-07 393 | 981 652 0451160916 2001-08-08 08:36:44-07 394 | 95 480 0590445065 2001-08-10 07:29:52-07 395 | 593 476 0694003611 2001-08-15 11:57:40-07 396 | 977 853 0679803335 2001-08-09 09:30:46-07 397 | 117 185 0760720002 2001-08-07 13:00:48-07 398 | 406 1123 0394900014 2001-08-13 09:47:04-07 399 | 340 1149 0385121679 2001-08-12 13:39:22-07 400 | 871 388 1885418035 2001-08-07 11:31:57-07 401 | 1000 221 039480001X 2001-09-14 16:46:32-07 402 | 1001 107 039480001X 2001-09-14 17:42:22-07 403 | 754 107 0394800753 2001-08-11 09:55:05-07 404 | 458 107 0394800753 2001-08-07 10:58:36-07 405 | 189 107 0394800753 2001-08-06 11:46:36-07 406 | 720 107 0394800753 2001-08-08 10:46:13-07 407 | 1002 107 0394800753 2001-09-22 11:23:28-07 408 | 2 107 0394800753 2001-09-22 20:58:56-07 409 | \. 410 | -- 411 | -- Data for TOC Entry ID 121 (OID 3391454) 412 | -- 413 | -- Name: customers Type: TABLE DATA Owner: manager 414 | -- 415 | 416 | 417 | COPY "customers" FROM stdin; 418 | 107 Jackson Annie 419 | 112 Gould Ed 420 | 142 Allen Chad 421 | 146 Williams James 422 | 172 Brown Richard 423 | 185 Morrill Eric 424 | 221 King Jenny 425 | 270 Bollman Julie 426 | 388 Morrill Royce 427 | 409 Holloway Christine 428 | 430 Black Jean 429 | 476 Clark James 430 | 480 Thomas Rich 431 | 488 Young Trevor 432 | 574 Bennett Laura 433 | 652 Anderson Jonathan 434 | 655 Olson Dave 435 | 671 Brown Chuck 436 | 723 Eisele Don 437 | 724 Holloway Adam 438 | 738 Gould Shirley 439 | 830 Robertson Royce 440 | 853 Black Wendy 441 | 860 Owens Tim 442 | 880 Robinson Tammy 443 | 898 Gerdes Kate 444 | 964 Gould Ramon 445 | 1045 Owens Jean 446 | 1125 Bollman Owen 447 | 1149 Becker Owen 448 | 1123 Corner Kathy 449 | \. 450 | -- 451 | -- Data for TOC Entry ID 122 (OID 3574043) 452 | -- 453 | -- Name: book_queue Type: TABLE DATA Owner: postgres 454 | -- 455 | 456 | 457 | COPY "book_queue" FROM stdin; 458 | Learning Python 7805 4 t 459 | Perl Cookbook 7806 4 t 460 | \. 461 | -- 462 | -- Data for TOC Entry ID 123 (OID 3574983) 463 | -- 464 | -- Name: stock_backup Type: TABLE DATA Owner: postgres 465 | -- 466 | 467 | 468 | COPY "stock_backup" FROM stdin; 469 | 0385121679 29.00 36.95 65 470 | 039480001X 30.00 32.95 31 471 | 0394800753 16.00 16.95 0 472 | 0394900014 23.00 23.95 0 473 | 044100590X 36.00 45.95 89 474 | 0441172717 17.00 21.95 77 475 | 0451160916 24.00 28.95 22 476 | 0451198492 36.00 46.95 0 477 | 0451457994 17.00 22.95 0 478 | 0590445065 23.00 23.95 10 479 | 0679803335 20.00 24.95 18 480 | 0694003611 25.00 28.95 50 481 | 0760720002 18.00 23.95 28 482 | 0823015505 26.00 28.95 16 483 | 0929605942 19.00 21.95 25 484 | 1885418035 23.00 24.95 77 485 | \. 486 | -- 487 | -- Data for TOC Entry ID 124 (OID 3628247) 488 | -- 489 | -- Name: favorite_books Type: TABLE DATA Owner: manager 490 | -- 491 | 492 | 493 | COPY "favorite_books" FROM stdin; 494 | 102 {"The Hitchhiker's Guide to the Galaxy","The Restauraunt at the End of the Universe"} 495 | 103 {"There and Back Again: A Hobbit's Holiday","Kittens Squared"} 496 | \. 497 | -- 498 | -- Data for TOC Entry ID 125 (OID 3628899) 499 | -- 500 | -- Name: employees Type: TABLE DATA Owner: postgres 501 | -- 502 | 503 | 504 | COPY "employees" FROM stdin; 505 | 101 Appel Vincent 506 | 102 Holloway Michael 507 | 105 Connoly Sarah 508 | 104 Noble Ben 509 | 103 Joble David 510 | 106 Hall Timothy 511 | 1008 Williams \N 512 | \. 513 | -- 514 | -- Data for TOC Entry ID 126 (OID 3629174) 515 | -- 516 | -- Name: editions Type: TABLE DATA Owner: manager 517 | -- 518 | 519 | 520 | COPY "editions" FROM stdin; 521 | 039480001X 1608 1 59 1957-03-01 h 522 | 0451160916 7808 1 75 1981-08-01 p 523 | 0394800753 1590 1 59 1949-03-01 p 524 | 0590445065 25908 1 150 1987-03-01 p 525 | 0694003611 1501 1 65 1947-03-04 p 526 | 0679803335 1234 1 102 1922-01-01 p 527 | 0760720002 190 1 91 1868-01-01 p 528 | 0394900014 1608 1 59 1957-01-01 p 529 | 0385121679 7808 2 75 1993-10-01 h 530 | 1885418035 156 1 163 1995-03-28 p 531 | 0929605942 156 2 171 1998-12-01 p 532 | 0441172717 4513 2 99 1998-09-01 p 533 | 044100590X 4513 3 99 1999-10-01 h 534 | 0451457994 4267 3 101 2000-09-12 p 535 | 0451198492 4267 3 101 1999-10-01 h 536 | 0823015505 2038 1 62 1958-01-01 p 537 | 0596000855 41473 2 113 2001-03-01 p 538 | \. 539 | -- 540 | -- Data for TOC Entry ID 127 (OID 3629264) 541 | -- 542 | -- Name: books Type: TABLE DATA Owner: manager 543 | -- 544 | 545 | 546 | COPY "books" FROM stdin; 547 | 7808 The Shining 4156 9 548 | 4513 Dune 1866 15 549 | 4267 2001: A Space Odyssey 2001 15 550 | 1608 The Cat in the Hat 1809 2 551 | 1590 Bartholomew and the Oobleck 1809 2 552 | 25908 Franklin in the Dark 15990 2 553 | 1501 Goodnight Moon 2031 2 554 | 190 Little Women 16 6 555 | 1234 The Velveteen Rabbit 25041 3 556 | 2038 Dynamic Anatomy 1644 0 557 | 156 The Tell-Tale Heart 115 9 558 | 41473 Programming Python 7805 4 559 | 41477 Learning Python 7805 4 560 | 41478 Perl Cookbook 7806 4 561 | 41472 Practical PostgreSQL 1212 4 562 | \. 563 | -- 564 | -- Data for TOC Entry ID 128 (OID 3629424) 565 | -- 566 | -- Name: distinguished_authors Type: TABLE DATA Owner: manager 567 | -- 568 | 569 | 570 | COPY "distinguished_authors" FROM stdin; 571 | 25043 Simon Neil Pulitzer Prize 572 | 1809 Geisel Theodor Seuss Pulitzer Prize 573 | \. 574 | -- 575 | -- Data for TOC Entry ID 129 (OID 3727889) 576 | -- 577 | -- Name: favorite_authors Type: TABLE DATA Owner: manager 578 | -- 579 | 580 | 581 | COPY "favorite_authors" FROM stdin; 582 | 102 {{"J.R.R. Tolkien","The Silmarillion"},{"Charles Dickens","Great Expectations"},{"Ariel Denham","Attic Lives"}} 583 | \. 584 | -- 585 | -- Data for TOC Entry ID 130 (OID 3751599) 586 | -- 587 | -- Name: text_sorting Type: TABLE DATA Owner: postgres 588 | -- 589 | 590 | 591 | COPY "text_sorting" FROM stdin; 592 | 0 593 | 1 594 | 2 595 | 3 596 | A 597 | B 598 | C 599 | D 600 | a 601 | b 602 | c 603 | d 604 | \. 605 | -- 606 | -- Data for TOC Entry ID 131 (OID 3751882) 607 | -- 608 | -- Name: subjects Type: TABLE DATA Owner: postgres 609 | -- 610 | 611 | 612 | COPY "subjects" FROM stdin; 613 | 0 Arts Creativity St 614 | 1 Business Productivity Ave 615 | 2 Children's Books Kids Ct 616 | 3 Classics Academic Rd 617 | 4 Computers Productivity Ave 618 | 5 Cooking Creativity St 619 | 6 Drama Main St 620 | 7 Entertainment Main St 621 | 8 History Academic Rd 622 | 9 Horror Black Raven Dr 623 | 10 Mystery Black Raven Dr 624 | 11 Poetry Sunset Dr 625 | 12 Religion \N 626 | 13 Romance Main St 627 | 14 Science Productivity Ave 628 | 15 Science Fiction Main St 629 | \. 630 | -- 631 | -- Data for TOC Entry ID 132 (OID 3751975) 632 | -- 633 | -- Name: alternate_stock Type: TABLE DATA Owner: postgres 634 | -- 635 | 636 | 637 | COPY "alternate_stock" FROM stdin; 638 | 0385121679 29.00 36.95 65 639 | 039480001X 30.00 32.95 31 640 | 0394900014 23.00 23.95 0 641 | 044100590X 36.00 45.95 89 642 | 0441172717 17.00 21.95 77 643 | 0451160916 24.00 28.95 22 644 | 0451198492 36.00 46.95 0 645 | 0451457994 17.00 22.95 0 646 | 0590445065 23.00 23.95 10 647 | 0679803335 20.00 24.95 18 648 | 0694003611 25.00 28.95 50 649 | 0760720002 18.00 23.95 28 650 | 0823015505 26.00 28.95 16 651 | 0929605942 19.00 21.95 25 652 | 1885418035 23.00 24.95 77 653 | 0394800753 16.00 16.95 4 654 | \. 655 | -- 656 | -- Data for TOC Entry ID 133 (OID 3752020) 657 | -- 658 | -- Name: book_backup Type: TABLE DATA Owner: postgres 659 | -- 660 | 661 | 662 | COPY "book_backup" FROM stdin; 663 | 7808 The Shining 4156 9 664 | 4513 Dune 1866 15 665 | 4267 2001: A Space Odyssey 2001 15 666 | 1608 The Cat in the Hat 1809 2 667 | 1590 Bartholomew and the Oobleck 1809 2 668 | 25908 Franklin in the Dark 15990 2 669 | 1501 Goodnight Moon 2031 2 670 | 190 Little Women 16 6 671 | 1234 The Velveteen Rabbit 25041 3 672 | 2038 Dynamic Anatomy 1644 0 673 | 156 The Tell-Tale Heart 115 9 674 | 41472 Practical PostgreSQL 1212 4 675 | 41473 Programming Python 7805 4 676 | 41477 Learning Python 7805 4 677 | 41478 Perl Cookbook 7806 4 678 | 7808 The Shining 4156 9 679 | 4513 Dune 1866 15 680 | 4267 2001: A Space Odyssey 2001 15 681 | 1608 The Cat in the Hat 1809 2 682 | 1590 Bartholomew and the Oobleck 1809 2 683 | 25908 Franklin in the Dark 15990 2 684 | 1501 Goodnight Moon 2031 2 685 | 190 Little Women 16 6 686 | 1234 The Velveteen Rabbit 25041 3 687 | 2038 Dynamic Anatomy 1644 0 688 | 156 The Tell-Tale Heart 115 9 689 | 41473 Programming Python 7805 4 690 | 41477 Learning Python 7805 4 691 | 41478 Perl Cookbook 7806 4 692 | 41472 Practical PostgreSQL 1212 4 693 | \. 694 | -- 695 | -- Data for TOC Entry ID 134 (OID 4063343) 696 | -- 697 | -- Name: schedules Type: TABLE DATA Owner: postgres 698 | -- 699 | 700 | 701 | COPY "schedules" FROM stdin; 702 | 102 Mon - Fri, 9am - 5pm 703 | \. 704 | COMMIT; 705 | 706 | -------------------------------------------------------------------------------- /dump_fdw.c: -------------------------------------------------------------------------------- 1 | /* ========================================================================= */ 2 | /* Copyright (c) 2015 MeetMe, Inc. All Rights Reserved. */ 3 | /* ========================================================================= */ 4 | 5 | /** 6 | * @file dump_fdw.c 7 | * @brief Foreign Data Wrapper for Querying PostgreSQL Dump Files 8 | * @author Jonah H. Harris 9 | * @date 2015 10 | * @copyright The New BSD License 11 | * 12 | * NOTE XXX FIX 13 | * 14 | * THIS IS A MIMIMALLY VIABLE PRODUCT AND SHOULD NOT BE USED IN PRODUCTION! 15 | * 16 | * Most buffers are hard-coded with an initial allocation and there isn't 17 | * any bounds checking. So, basically, any row > 16MB is going to have an 18 | * issue. This will be fixed later, but it works for the MVP as required 19 | * for use. 20 | * 21 | * THERE ARE PROBABLY MEMORY LEAKS AND SEGFAULTS AHEAD - THIS IS A PROTOTYPE 22 | */ 23 | 24 | #include "postgres.h" 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "funcapi.h" 31 | #include "commands/defrem.h" 32 | #include "access/htup_details.h" 33 | #include "access/reloptions.h" 34 | #include "foreign/fdwapi.h" 35 | #include "catalog/pg_foreign_table.h" 36 | #include "foreign/foreign.h" 37 | #include "foreign/fdwapi.h" 38 | #include "optimizer/pathnode.h" 39 | #include "miscadmin.h" 40 | #include "optimizer/planmain.h" 41 | #include "optimizer/restrictinfo.h" 42 | #include "utils/builtins.h" 43 | #include "utils/rel.h" 44 | 45 | #include "csv_parser.h" 46 | 47 | PG_MODULE_MAGIC; 48 | 49 | #define ZLIB_IN_SIZE 4096 50 | #define ZLIB_OUT_SIZE 4096 51 | #define MIN(x, y) ((x < y) ? y : x) 52 | #define MAX(x, y) ((x > y) ? y : x) 53 | 54 | /** 55 | * A chained string 56 | */ 57 | typedef struct string_t { 58 | size_t length; /**< length of this segment (in bytes) */ 59 | struct string_t *next; /**< pointer to the next segment */ 60 | char data[1]; /**< data for this segment */ 61 | } string_t; 62 | 63 | /** 64 | * A field in the TSV 65 | */ 66 | typedef struct field_t { 67 | string_t *data; /**< the data for this field */ 68 | int row; /**< the row number in the TSV */ 69 | int col; /**< the col number in the row */ 70 | struct field_t *next; /**< pointer to the next field */ 71 | } field_t; 72 | 73 | /** 74 | * Stashed in fdw_private 75 | */ 76 | typedef struct { 77 | char *file_name; /**< the path and name of the dump file */ 78 | char *schema_name; /**< the namespace of the table */ 79 | char *relation_name; /**< the table to read */ 80 | BlockNumber pages; /**< storage size (in pages) estimate */ 81 | double ntuples; /**< cardinality estimate */ 82 | } DumpFdwPlanState; 83 | 84 | /** 85 | * The stateful structure used during query execution. 86 | */ 87 | typedef struct DumpFdwExecutionState { 88 | char *file_name; /**< the path and name of the dump file */ 89 | char *schema_name; /**< the namespace of the table */ 90 | char *relation_name; /**< the table to read */ 91 | off_t offset; /**< file offset to table data */ 92 | int fd; /**< dump file descriptor */ 93 | csv_parser_t parser; /**< CSV parser */ 94 | csv_parser_settings_t settings; /**< CSV parser settings */ 95 | int tuple_count; /**< cardinality of tuple store */ 96 | bool at_eof; /**< have we hit the end of the data */ 97 | size_t buflen; /**< length of compressed data (in bytes) */ 98 | z_streamp zp; /**< zlib stream */ 99 | char *out; /**< decompressed data buffer */ 100 | char *buf; /**< compressed data buffer */ 101 | uint8_t *tbuf; /**< scratch buffer for row data */ 102 | uint8_t *tptr; /**< pointer to offset in scratch buffer */ 103 | uint8_t *tend; /**< end of the scratch buffer */ 104 | field_t *head; /**< head of tuple attribute linked-list */ 105 | field_t *tail; /**< tail of tuple attribute linked-list */ 106 | AttInMetadata *attinmeta; /**< relation attribute metadata */ 107 | uint8_t *single_tuple_buffer; /**< buffer for building tuples */ 108 | Tuplestorestate *tupstore; /**< store used for decompressed tuples */ 109 | TupleTableSlot *ts; /**< temporary slot for use with tuple store */ 110 | } DumpFdwExecutionState; 111 | 112 | /* 113 | * SQL functions 114 | */ 115 | extern Datum dump_fdw_handler(PG_FUNCTION_ARGS); 116 | PG_FUNCTION_INFO_V1(dump_fdw_handler); 117 | extern Datum dump_fdw_validator(PG_FUNCTION_ARGS); 118 | PG_FUNCTION_INFO_V1(dump_fdw_validator); 119 | 120 | /* callback functions */ 121 | static void dumpGetForeignRelSize (PlannerInfo *, RelOptInfo *, Oid); 122 | static void dumpGetForeignPaths (PlannerInfo *, RelOptInfo *, Oid); 123 | static ForeignScan *dumpGetForeignPlan (PlannerInfo *, RelOptInfo *, Oid, 124 | ForeignPath *, List *, List *); 125 | static void dumpBeginForeignScan (ForeignScanState *, int); 126 | static TupleTableSlot *dumpIterateForeignScan (ForeignScanState *); 127 | static void dumpReScanForeignScan (ForeignScanState *); 128 | static void dumpEndForeignScan (ForeignScanState *); 129 | static void dumpExplainForeignScan (ForeignScanState *, struct ExplainState *); 130 | static bool dumpAnalyzeForeignTable (Relation, AcquireSampleRowsFunc *, 131 | BlockNumber *); 132 | string_t *new_string (DumpFdwExecutionState *, const char *, size_t); 133 | field_t *new_field (DumpFdwExecutionState *, const char *, size_t, int, int); 134 | static void dumpGetOptions (Oid, char **, char **, char **); 135 | static inline int read_int (int); 136 | static inline char *read_str (int, bool); 137 | static inline size_t pgdump_read (int, char **, size_t *); 138 | static void read_toc (DumpFdwExecutionState *); 139 | int field_cb (csv_parser_t *, const char *, size_t, int, int); 140 | 141 | /* ========================================================================= */ 142 | /* -- EXPORTED FUNCTIONS --------------------------------------------------- */ 143 | /* ========================================================================= */ 144 | 145 | Datum 146 | dump_fdw_handler ( 147 | PG_FUNCTION_ARGS 148 | ) { 149 | FdwRoutine *fdwroutine = makeNode(FdwRoutine); 150 | 151 | /* Required callbacks */ 152 | fdwroutine->GetForeignRelSize = dumpGetForeignRelSize; 153 | fdwroutine->GetForeignPaths = dumpGetForeignPaths; 154 | fdwroutine->GetForeignPlan = dumpGetForeignPlan; 155 | fdwroutine->BeginForeignScan = dumpBeginForeignScan; 156 | fdwroutine->IterateForeignScan = dumpIterateForeignScan; 157 | fdwroutine->ReScanForeignScan = dumpReScanForeignScan; 158 | fdwroutine->EndForeignScan = dumpEndForeignScan; 159 | 160 | /* Optional (modification-related) callbacks */ 161 | fdwroutine->AddForeignUpdateTargets = NULL; 162 | fdwroutine->PlanForeignModify = NULL; 163 | fdwroutine->BeginForeignModify = NULL; 164 | fdwroutine->ExecForeignInsert = NULL; 165 | fdwroutine->ExecForeignUpdate = NULL; 166 | fdwroutine->ExecForeignDelete = NULL; 167 | fdwroutine->EndForeignModify = NULL; 168 | 169 | /* EXPLAIN-related callbacks */ 170 | fdwroutine->ExplainForeignScan = dumpExplainForeignScan; 171 | fdwroutine->ExplainForeignModify = NULL; 172 | 173 | /* ANALYZE-related callbacks */ 174 | fdwroutine->AnalyzeForeignTable = dumpAnalyzeForeignTable; 175 | 176 | PG_RETURN_POINTER(fdwroutine); 177 | 178 | } /* dump_fdw_handler() */ 179 | 180 | /* ------------------------------------------------------------------------- */ 181 | 182 | Datum 183 | dump_fdw_validator ( 184 | PG_FUNCTION_ARGS 185 | ) { 186 | List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); 187 | Oid catalog = PG_GETARG_OID(1); 188 | char *file_name = NULL; 189 | char *schema_name = NULL; 190 | char *relation_name = NULL; 191 | ListCell *cell; 192 | 193 | PG_RETURN_VOID(); 194 | 195 | /* Only permit superusers to set the options on a dump_fdw table */ 196 | if (ForeignTableRelationId == catalog 197 | && false == superuser()) { 198 | ereport(ERROR, 199 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), 200 | errmsg("only a superuser can set options of a dump_fdw table"))); 201 | } 202 | 203 | foreach(cell, options_list) { 204 | DefElem *def = (DefElem *) lfirst(cell); 205 | 206 | if (0 == strcmp(def->defname, "file_name")) { 207 | if (NULL != file_name) { 208 | ereport(ERROR, 209 | (errcode(ERRCODE_SYNTAX_ERROR), 210 | errmsg("conflicting or redundant options"))); 211 | } 212 | file_name = defGetString(def); 213 | } else if (0 == strcmp(def->defname, "schema_name")) { 214 | if (NULL != schema_name) { 215 | ereport(ERROR, 216 | (errcode(ERRCODE_SYNTAX_ERROR), 217 | errmsg("conflicting or redundant options"))); 218 | } 219 | schema_name = defGetString(def); 220 | } else if (0 == strcmp(def->defname, "relation_name")) { 221 | if (NULL != relation_name) { 222 | ereport(ERROR, 223 | (errcode(ERRCODE_SYNTAX_ERROR), 224 | errmsg("conflicting or redundant options"))); 225 | } 226 | relation_name = defGetString(def); 227 | } else { 228 | ereport(ERROR, 229 | (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), 230 | errmsg("unsupported option \"%s\"", def->defname))); 231 | } 232 | } 233 | 234 | /* 235 | * file_name option is required for dump_fdw foreign tables. 236 | */ 237 | if (!(ForeignTableRelationId == catalog 238 | && NULL != file_name 239 | && NULL != schema_name 240 | && NULL != relation_name)) { 241 | ereport(ERROR, 242 | (errcode(ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED), 243 | errmsg("file_name, schema_name, and relation_name options are" 244 | " required for dump_fdw foreign tables"))); 245 | } 246 | 247 | /* 248 | * NOTE XXX FIX 249 | * 250 | * Rather than continuing here, we should probably validate the dump file 251 | * as containing the specified relation. As this will require a little 252 | * refactoring, it's not part of the MVP. 253 | */ 254 | 255 | PG_RETURN_VOID(); 256 | 257 | } /* dump_fdw_validator */ 258 | 259 | /* ========================================================================= */ 260 | /* -- STATIC FUNCTIONS ----------------------------------------------------- */ 261 | /* ========================================================================= */ 262 | 263 | static void 264 | dumpGetOptions ( 265 | Oid foreigntableid, 266 | char **file_name, 267 | char **schema_name, 268 | char **relation_name 269 | ) { 270 | ForeignTable *table; 271 | ForeignServer *server; 272 | ForeignDataWrapper *wrapper; 273 | List *options; 274 | ListCell *lc; 275 | ListCell *prev; 276 | 277 | table = GetForeignTable(foreigntableid); 278 | server = GetForeignServer(table->serverid); 279 | wrapper = GetForeignDataWrapper(server->fdwid); 280 | 281 | options = NIL; 282 | options = list_concat(options, wrapper->options); 283 | options = list_concat(options, server->options); 284 | options = list_concat(options, table->options); 285 | 286 | /* 287 | * Separate out the file, schema, and relation names. 288 | */ 289 | *file_name = NULL; 290 | *schema_name = NULL; 291 | *relation_name = NULL; 292 | prev = NULL; 293 | foreach(lc, options) 294 | { 295 | DefElem *def = (DefElem *) lfirst(lc); 296 | 297 | if (0 == strcmp(def->defname, "file_name")) { 298 | *file_name = defGetString(def); 299 | } else if (0 == strcmp(def->defname, "schema_name")) { 300 | *schema_name = defGetString(def); 301 | } else if (0 == strcmp(def->defname, "relation_name")) { 302 | *relation_name = defGetString(def); 303 | } 304 | prev = lc; 305 | } 306 | 307 | /* The validator should have prevented this but, check again, just in case */ 308 | if (NULL == *file_name) { 309 | elog(ERROR, "file_name is required for dump_fdw foreign tables"); 310 | } 311 | if (NULL == *schema_name) { 312 | elog(ERROR, "schema_name is required for dump_fdw foreign tables"); 313 | } 314 | if (NULL == *relation_name) { 315 | elog(ERROR, "relation_name is required for dump_fdw foreign tables"); 316 | } 317 | } /* dumpGetOptions() */ 318 | 319 | /* ------------------------------------------------------------------------- */ 320 | 321 | static void 322 | dumpGetForeignRelSize ( 323 | PlannerInfo *root, 324 | RelOptInfo *baserel, 325 | Oid foreigntableid 326 | ) { 327 | DumpFdwPlanState *fdw_private; 328 | 329 | elog(LOG, "%s is currently unimplemented", __func__); 330 | 331 | baserel->rows = 0; 332 | 333 | fdw_private = palloc0(sizeof(DumpFdwPlanState)); 334 | baserel->fdw_private = (void *) fdw_private; 335 | } /* dumpGetForeignRelSize() */ 336 | 337 | /* ------------------------------------------------------------------------- */ 338 | 339 | static void 340 | dumpGetForeignPaths ( 341 | PlannerInfo *root, 342 | RelOptInfo *baserel, 343 | Oid foreigntableid 344 | ) { 345 | Cost startup_cost; 346 | Cost total_cost; 347 | 348 | elog(LOG, "%s is currently unimplemented", __func__); 349 | 350 | startup_cost = 0; 351 | total_cost = startup_cost + baserel->rows; 352 | 353 | /* Create a ForeignPath node and add it as only possible path */ 354 | add_path(baserel, (Path *) 355 | create_foreignscan_path(root, baserel, 356 | baserel->rows, 357 | startup_cost, 358 | total_cost, 359 | NIL, /* no pathkeys */ 360 | NULL, /* no outer rel either */ 361 | NIL)); /* no fdw_private data */ 362 | 363 | } /* dumpGetForeignPaths() */ 364 | 365 | /* ------------------------------------------------------------------------- */ 366 | 367 | static ForeignScan * 368 | dumpGetForeignPlan ( 369 | PlannerInfo *root, 370 | RelOptInfo *baserel, 371 | Oid foreigntableid, 372 | ForeignPath *best_path, 373 | List *tlist, 374 | List *scan_clauses 375 | ) { 376 | Index scan_relid = baserel->relid; 377 | 378 | elog(LOG, "%s is currently unimplemented", __func__); 379 | 380 | scan_clauses = extract_actual_clauses(scan_clauses, false); 381 | 382 | /* Create the ForeignScan node */ 383 | return make_foreignscan(tlist, 384 | scan_clauses, 385 | scan_relid, 386 | NIL, /* no expressions to evaluate */ 387 | NIL); /* no private state either */ 388 | 389 | } /* dumpGetForeignPlan() */ 390 | 391 | /* ------------------------------------------------------------------------- */ 392 | 393 | static void 394 | dumpBeginForeignScan ( 395 | ForeignScanState *node, 396 | int eflags 397 | ) { 398 | DumpFdwExecutionState *festate; 399 | 400 | festate = (DumpFdwExecutionState *) palloc(sizeof(DumpFdwExecutionState)); 401 | 402 | dumpGetOptions(RelationGetRelid(node->ss.ss_currentRelation), 403 | &festate->file_name, &festate->schema_name, &festate->relation_name); 404 | 405 | festate->tupstore = tuplestore_begin_heap(true, false, 32768); 406 | festate->single_tuple_buffer = palloc(1024 * 1024 * 16); 407 | 408 | festate->zp = (z_streamp) palloc(sizeof(z_stream)); 409 | festate->zp->zalloc = Z_NULL; 410 | festate->zp->zfree = Z_NULL; 411 | festate->zp->opaque = Z_NULL; 412 | 413 | festate->buf = palloc(ZLIB_IN_SIZE); 414 | festate->buflen = ZLIB_IN_SIZE; 415 | 416 | festate->out = palloc(ZLIB_OUT_SIZE + 1); 417 | 418 | festate->settings.delimiter = '\t'; 419 | festate->settings.field_cb = field_cb; 420 | 421 | csv_parser_init(&festate->parser); 422 | festate->parser.data = festate; 423 | festate->tbuf = palloc(1024 * 1024 * 32); 424 | festate->tptr = festate->tbuf; 425 | festate->tend = festate->tbuf + (1024 * 1024 * 32); 426 | 427 | festate->head = NULL; 428 | festate->tail = NULL; 429 | festate->at_eof = false; 430 | festate->tuple_count = 0; 431 | festate->offset = 0; 432 | 433 | festate->fd = open(festate->file_name, O_RDONLY); 434 | if (0 > festate->fd) { 435 | elog(ERROR, "Failed to open file (%s)", festate->file_name); 436 | } 437 | 438 | read_toc(festate); 439 | 440 | if (0 > lseek(festate->fd, festate->offset + 6, SEEK_SET)) { 441 | elog(ERROR, "Failed to seek to offset (%s)", festate->file_name); 442 | } 443 | 444 | if (Z_OK != inflateInit(festate->zp)) { 445 | elog(ERROR, "could not initialize compression library: %s", 446 | festate->zp->msg); 447 | } 448 | 449 | festate->attinmeta = 450 | TupleDescGetAttInMetadata(node->ss.ss_ScanTupleSlot->tts_tupleDescriptor); 451 | festate->ts = 452 | MakeSingleTupleTableSlot(node->ss.ss_ScanTupleSlot->tts_tupleDescriptor); 453 | 454 | node->fdw_state = (void *) festate; 455 | 456 | } /* dumpBeginForeignScan() */ 457 | 458 | /* ------------------------------------------------------------------------- */ 459 | 460 | static TupleTableSlot * 461 | dumpIterateForeignScan ( 462 | ForeignScanState *node 463 | ) { 464 | DumpFdwExecutionState *festate = (DumpFdwExecutionState *) node->fdw_state; 465 | TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; 466 | TupleDesc tupleDescriptor = slot->tts_tupleDescriptor; 467 | Datum *columnValues = slot->tts_values; 468 | bool *columnNulls = slot->tts_isnull; 469 | int columnCount = tupleDescriptor->natts; 470 | 471 | /* initialize all values for this row to null */ 472 | memset(columnValues, 0, columnCount * sizeof(Datum)); 473 | memset(columnNulls, true, columnCount * sizeof(bool)); 474 | ExecClearTuple(slot); 475 | 476 | /* 477 | * As tuples are decompressed in chunks, we temporarily hold them in 478 | * an in-memory tuplestore. On each iteration, we'll decompress the next 479 | * block iff the tuplestore is empty. In order to clean-up the tuplestore 480 | * memory as much as possible, we'll clear it here, rather than trying 481 | * to rely on tuplestore_trim. 482 | * 483 | * NOTE: This assumes each iteration results in more than one tuple being 484 | * extracted from the dump. While there are cases where this may not 485 | * always be true, it should work best for the 80% of cases. 486 | */ 487 | if (0 == festate->tuple_count) { 488 | tuplestore_clear(festate->tupstore); 489 | } 490 | 491 | /* 492 | * Loop until we either reach a full tuple or EOF. 493 | */ 494 | while (!(festate->at_eof || (0 != festate->tuple_count))) { 495 | int res = Z_OK; 496 | size_t cnt = 0; 497 | while ((cnt = pgdump_read(festate->fd, &festate->buf, &festate->buflen))) { 498 | festate->zp->next_in = (void *) festate->buf; 499 | festate->zp->avail_in = cnt; 500 | while (festate->zp->avail_in > 0) { 501 | festate->zp->next_out = (void *) festate->out; 502 | festate->zp->avail_out = ZLIB_OUT_SIZE; 503 | 504 | res = inflate(festate->zp, 0); 505 | if (Z_OK != res && Z_STREAM_END != res) { 506 | elog(ERROR, "could not uncompress data: %s", festate->zp->msg); 507 | } 508 | 509 | festate->out[ZLIB_OUT_SIZE - festate->zp->avail_out] = '\0'; 510 | csv_parser_execute(&festate->parser, &festate->settings, festate->out, 511 | (ZLIB_OUT_SIZE - festate->zp->avail_out)); 512 | } 513 | if (0 != festate->tuple_count) { 514 | goto post_loop; 515 | } 516 | } 517 | 518 | festate->at_eof = true; 519 | festate->zp->next_in = NULL; 520 | festate->zp->avail_in = 0; 521 | 522 | while (Z_STREAM_END != res) { 523 | festate->zp->next_out = (void *) festate->out; 524 | festate->zp->avail_out = ZLIB_OUT_SIZE; 525 | 526 | res = inflate(festate->zp, 0); 527 | if (Z_OK != res && Z_STREAM_END != res) { 528 | elog(ERROR, "could not uncompress data: %s", festate->zp->msg); 529 | } 530 | 531 | festate->out[ZLIB_OUT_SIZE - festate->zp->avail_out] = '\0'; 532 | csv_parser_execute(&festate->parser, &festate->settings, festate->out, 533 | (ZLIB_OUT_SIZE - festate->zp->avail_out)); 534 | } 535 | 536 | if (Z_OK != inflateEnd(festate->zp)) { 537 | elog(ERROR, "could not close compression library: %s", 538 | festate->zp->msg); 539 | } 540 | } 541 | 542 | post_loop: 543 | 544 | if (0 != festate->tuple_count) { 545 | TupleTableSlot *ts = festate->ts; 546 | if (true == tuplestore_gettupleslot(festate->tupstore, true, false, ts)) { 547 | heap_deform_tuple(ts->tts_tuple, ts->tts_tupleDescriptor, 548 | columnValues, columnNulls); 549 | ExecStoreVirtualTuple(slot); 550 | } else { 551 | elog(ERROR, "Issue getting tuple from store"); 552 | } 553 | --festate->tuple_count; 554 | } 555 | 556 | /* then return the slot */ 557 | return slot; 558 | 559 | } /* dumpIterateForeignScan() */ 560 | 561 | /* ------------------------------------------------------------------------- */ 562 | 563 | static void 564 | dumpReScanForeignScan ( 565 | ForeignScanState *node 566 | ) { 567 | DumpFdwExecutionState *festate = (DumpFdwExecutionState *) node->fdw_state; 568 | 569 | festate->tptr = festate->tbuf; 570 | festate->head = NULL; 571 | festate->tail = NULL; 572 | festate->at_eof = false; 573 | festate->tuple_count = 0; 574 | tuplestore_clear(festate->tupstore); 575 | csv_parser_init(&festate->parser); 576 | festate->parser.data = festate; 577 | 578 | if (0 > lseek(festate->fd, festate->offset + 6, SEEK_SET)) { 579 | elog(ERROR, "Failed to seek to offset (%s)", festate->file_name); 580 | } 581 | 582 | if (Z_OK != inflateInit(festate->zp)) { 583 | elog(ERROR, "could not initialize compression library: %s", 584 | festate->zp->msg); 585 | } 586 | 587 | } /* dumpReScanForeignScan() */ 588 | 589 | /* ------------------------------------------------------------------------- */ 590 | 591 | static void 592 | dumpEndForeignScan ( 593 | ForeignScanState *node 594 | ) { 595 | DumpFdwExecutionState *festate = (DumpFdwExecutionState *) node->fdw_state; 596 | 597 | pfree(festate->file_name); 598 | pfree(festate->schema_name); 599 | pfree(festate->relation_name); 600 | pfree(festate->single_tuple_buffer); 601 | pfree(festate->zp); 602 | pfree(festate->buf); 603 | pfree(festate->out); 604 | pfree(festate->tbuf); 605 | 606 | close(festate->fd); 607 | 608 | ExecDropSingleTupleTableSlot(festate->ts); 609 | tuplestore_end(festate->tupstore); 610 | 611 | pfree(festate); 612 | 613 | } /* dumpEndForeignScan() */ 614 | 615 | /* ------------------------------------------------------------------------- */ 616 | 617 | static void 618 | dumpExplainForeignScan ( 619 | ForeignScanState *node, 620 | struct ExplainState *es 621 | ) { 622 | elog(LOG, "%s is currently unimplemented", __func__); 623 | } /* dumpExplainForeignScan() */ 624 | 625 | /* ------------------------------------------------------------------------- */ 626 | 627 | static bool 628 | dumpAnalyzeForeignTable ( 629 | Relation relation, 630 | AcquireSampleRowsFunc *func, 631 | BlockNumber *totalpages 632 | ) { 633 | elog(LOG, "%s is currently unimplemented", __func__); 634 | 635 | return false; 636 | 637 | } /* dumpAnalyzeForeignTable() */ 638 | 639 | /* ------------------------------------------------------------------------- */ 640 | 641 | static inline int 642 | read_int ( 643 | int fd 644 | ) { 645 | uint8_t sign = 0; 646 | read(fd, &sign, 1); 647 | unsigned val = 0; 648 | read(fd, &val, 4); 649 | return (0 == sign) ? val : (-val); 650 | } 651 | 652 | /* ------------------------------------------------------------------------- */ 653 | 654 | static inline char * 655 | read_str ( 656 | int fd, 657 | bool skip 658 | ) { 659 | int len = read_int(fd); 660 | if (-1 == len) { 661 | return NULL; 662 | } else { 663 | if (skip) { 664 | lseek(fd, len, SEEK_CUR); 665 | return NULL; 666 | } 667 | char *str = palloc0(len + 1); 668 | read(fd, str, len); 669 | return str; 670 | } 671 | } 672 | 673 | /* ------------------------------------------------------------------------- */ 674 | 675 | static inline size_t 676 | pgdump_read ( 677 | int fd, 678 | char **buf, 679 | size_t *buflen 680 | ) { 681 | int blkLen; 682 | 683 | /* Read length */ 684 | blkLen = read_int(fd); 685 | if (0 == blkLen) { 686 | return 0; 687 | } 688 | 689 | /* If the caller's buffer is not large enough, allocate a bigger one */ 690 | if (blkLen > *buflen) 691 | { 692 | elog(WARNING, "having to re-alloc buf"); 693 | pfree(*buf); 694 | *buf = (char *) palloc(blkLen); 695 | *buflen = blkLen; 696 | } 697 | 698 | /* exits app on read errors */ 699 | read(fd, *buf, blkLen); 700 | 701 | return blkLen; 702 | 703 | } /* pgdump_read() */ 704 | 705 | /* ------------------------------------------------------------------------- */ 706 | 707 | static void 708 | read_toc ( 709 | DumpFdwExecutionState *festate 710 | ) { 711 | char buf[80] = { 0 }; 712 | uint8_t version_major; 713 | uint8_t version_minor; 714 | uint8_t version_revision; 715 | uint8_t size_of_int; 716 | uint8_t size_of_offset; 717 | uint8_t format; 718 | int compression; 719 | 720 | if (5 != read(festate->fd, buf, 5)) { 721 | elog(ERROR, "Failed to read dump file header"); 722 | } 723 | if (0 != memcmp(buf, "PGDMP", 5)) { 724 | elog(ERROR, "This doesn't look like a Postgres dump file"); 725 | } 726 | 727 | if (6 != read(festate->fd, buf, 6)) { 728 | elog(ERROR, "Failed to read dump file header"); 729 | } 730 | 731 | sscanf(&buf[0], "%c", &version_major); 732 | sscanf(&buf[1], "%c", &version_minor); 733 | sscanf(&buf[2], "%c", &version_revision); 734 | sscanf(&buf[3], "%c", &size_of_int); 735 | sscanf(&buf[4], "%c", &size_of_offset); 736 | sscanf(&buf[5], "%c", &format); 737 | 738 | /* It wouldn't be hard to support much else, but this is an MVP */ 739 | if (!(1 <= version_major 740 | && 10 <= version_minor 741 | && 4 == size_of_int 742 | && 8 == size_of_offset 743 | && 1 == format)) { 744 | elog(ERROR, "Unsupported Postgres dump file format"); 745 | } 746 | 747 | compression = read_int(festate->fd); 748 | if (-1 != compression) { 749 | elog(ERROR, "Unsupported Postgres dump compression"); 750 | } 751 | 752 | (void) read_int(festate->fd); /* tm_sec */ 753 | (void) read_int(festate->fd); /* tm_min */ 754 | (void) read_int(festate->fd); /* tm_hour */ 755 | (void) read_int(festate->fd); /* tm_mday */ 756 | (void) read_int(festate->fd); /* tm_mon */ 757 | (void) read_int(festate->fd); /* tm_year */ 758 | (void) read_int(festate->fd); /* tm_isdst */ 759 | 760 | (void) read_str(festate->fd, true); /* dbname */ 761 | (void) read_str(festate->fd, true); /* remote_version */ 762 | (void) read_str(festate->fd, true); /* version */ 763 | 764 | int toc_entry_count = read_int(festate->fd); 765 | int ii; 766 | for (ii = 0; ii < toc_entry_count; ++ii) { 767 | (void) read_int(festate->fd); /* dump_id */ 768 | size_t dumper = read_int(festate->fd); 769 | if (!(dumper == 0 || dumper == 1)) { 770 | elog(ERROR, "Unsupported Postgres dumper"); 771 | } 772 | 773 | (void) read_str(festate->fd, true); /* table_oid */ 774 | (void) read_str(festate->fd, true); /* oid */ 775 | char *tag = read_str(festate->fd, false); 776 | 777 | (void) read_str(festate->fd, true); /* desc */ 778 | int section = read_int(festate->fd); /* section */ 779 | 780 | (void) read_str(festate->fd, true); /* definition */ 781 | (void) read_str(festate->fd, true); /* drop statement */ 782 | (void) read_str(festate->fd, true); /* copy statement */ 783 | 784 | char *namespace = read_str(festate->fd, false); 785 | 786 | (void) read_str(festate->fd, true); /* tablespace */ 787 | (void) read_str(festate->fd, true); /* owner */ 788 | (void) read_str(festate->fd, true); /* with oids */ 789 | 790 | char *deps = read_str(festate->fd, false); 791 | while (NULL != deps) { 792 | pfree(deps); 793 | deps = read_str(festate->fd, false); 794 | } 795 | 796 | if (1 != read(festate->fd, buf, 1)) { 797 | elog(ERROR, "Failed to read offset setting"); 798 | } 799 | uint8_t offset_was_set = 0; 800 | uint64_t offset = 0; 801 | sscanf(&buf[0], "%c", &offset_was_set); 802 | if (offset_was_set) { 803 | if (8 != read(festate->fd, &offset, 8)) { 804 | elog(ERROR, "Failed to read offset"); 805 | } 806 | } 807 | 808 | if (tag 809 | && namespace 810 | && 3 == section 811 | && 0 == memcmp(festate->schema_name, namespace, 812 | MIN(strlen(namespace), strlen(festate->schema_name))) 813 | && 0 == memcmp(festate->relation_name, tag, 814 | MIN(strlen(tag), strlen(festate->relation_name)))) { 815 | festate->offset = offset; 816 | break; 817 | } 818 | } 819 | 820 | if (0 == festate->offset) { 821 | elog(ERROR, "Relation \"%s.%s\" does not exist in dump file.", 822 | festate->schema_name, festate->relation_name); 823 | } 824 | 825 | } /* read_toc() */ 826 | 827 | /* ------------------------------------------------------------------------- */ 828 | 829 | string_t * 830 | new_string ( 831 | DumpFdwExecutionState *festate, 832 | const char *data, 833 | size_t length 834 | ) { 835 | string_t *str = (string_t *) festate->tptr; 836 | str->length = length; 837 | str->next = NULL; 838 | memcpy(str->data, data, length); 839 | festate->tptr += sizeof(size_t) + sizeof(string_t *) + length; 840 | 841 | return str; 842 | 843 | } /* new_string() */ 844 | 845 | /* ------------------------------------------------------------------------- */ 846 | 847 | field_t * 848 | new_field ( 849 | DumpFdwExecutionState *festate, 850 | const char *data, 851 | size_t length, 852 | int row, 853 | int col 854 | ) { 855 | field_t *field = (field_t *) festate->tptr; 856 | memset(field, 0, sizeof(field_t)); 857 | field->row = row; 858 | field->col = col; 859 | field->next = NULL; 860 | festate->tptr += sizeof(field_t); 861 | field->data = new_string(festate, data, length); 862 | 863 | return field; 864 | 865 | } /* new_field() */ 866 | 867 | /* ------------------------------------------------------------------------- */ 868 | 869 | int 870 | field_cb ( 871 | csv_parser_t *parser, 872 | const char *data, 873 | size_t length, 874 | int row, 875 | int col 876 | ) { 877 | DumpFdwExecutionState *festate = parser->data; 878 | field_t *field; 879 | 880 | if (festate->tptr >= festate->tend) { 881 | elog(PANIC, "if we haven't crashed by now, we should have!"); 882 | } 883 | if (!festate->tail) { 884 | /* This is the a new row */ 885 | field = new_field(festate, data, length, row, col); 886 | festate->tail = field; 887 | festate->head = field; 888 | } else { 889 | if (festate->tail->row == row && festate->tail->col == col) { 890 | /* This is the same field as the last callback */ 891 | string_t *str = new_string(festate, data, length); 892 | festate->tail->data->next = str; 893 | } else if (festate->tail->row == row) { 894 | /* This is the a new field in the same row as the last callback */ 895 | field = new_field(festate, data, length, row, col); 896 | festate->tail->next = field; 897 | festate->tail = field; 898 | } else { 899 | /* This is a new row */ 900 | char *cstr[1600] = { NULL }; 901 | char *ptr = (char *) festate->single_tuple_buffer; 902 | int ii = 0; 903 | field_t *tfld = festate->head; 904 | while (tfld) { 905 | cstr[ii] = ptr; 906 | string_t *str = tfld->data; 907 | while (NULL != str) { 908 | memmove(ptr, str->data, str->length); 909 | ptr += str->length; 910 | str = str->next; 911 | } 912 | *ptr = '\0'; 913 | ++ptr; 914 | ++ii; 915 | tfld = tfld->next; 916 | } 917 | HeapTuple ret_tuple = BuildTupleFromCStrings(festate->attinmeta, cstr); 918 | tuplestore_puttuple(festate->tupstore, ret_tuple); 919 | heap_freetuple(ret_tuple); 920 | ++festate->tuple_count; 921 | festate->tptr = festate->tbuf; 922 | 923 | field = new_field(festate, data, length, row, col); 924 | festate->tail = field; 925 | festate->head = field; 926 | } 927 | } 928 | 929 | return 0; 930 | 931 | } /* field_cb() */ 932 | /* vi: set et sw=2 ts=2: */ 933 | 934 | --------------------------------------------------------------------------------