mirror of
https://github.com/levinsv/pgadmin3.git
synced 2026-05-15 06:05:49 -06:00
Добавлено использование автоподстановки в окно редактирования процедур и функции. Добавлена поддержка UTF-8. Добавлена поддержка unicode для идентификаторов. Добавлено сохранение выбранных опций в диалоге Выравнивания.
505 lines
13 KiB
C
505 lines
13 KiB
C
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// pgAdmin III - Tab completion
|
|
//
|
|
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
|
|
// This software is released under the PostgreSQL Licence
|
|
//
|
|
// tab-complete.cpp - Functions interfacing with tab-complete from psql
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* BUG: Must compile as C and not C++, because of
|
|
* http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B315481
|
|
* Adds a bit of C<->C++ cruft...
|
|
*/
|
|
|
|
#define _CRT_NONSTDC_NO_DEPRECATE
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <libpq-fe.h>
|
|
#include <ctype.h>
|
|
|
|
/*
|
|
* Callbacks to the C++ world
|
|
*/
|
|
char *pg_query_to_single_ordered_string(char *query, void *dbptr);
|
|
int get_id_encoding(void *dbptr);
|
|
|
|
/*
|
|
* Global vars for readline emulation
|
|
*/
|
|
static char *rl_line_buffer;
|
|
|
|
/*
|
|
* Macros to ease typing present in psql, rewritten for our API
|
|
*/
|
|
#define COMPLETE_WITH_QUERY(query) \
|
|
do { matches = complete_from_query(text,query,NULL,dbptr); } while (0)
|
|
#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
|
|
do { matches = complete_from_schema_query(text,&query, addon, dbptr); } while (0)
|
|
#define COMPLETE_WITH_LIST(list) \
|
|
do { matches = complete_from_list(text,list); } while (0)
|
|
#define COMPLETE_WITH_CONST(str) \
|
|
do { matches = complete_from_const(text,str); } while (0)
|
|
#define COMPLETE_WITH_ATTR(table) \
|
|
do { matches = complete_from_query(text,Query_for_list_of_attributes, table, dbptr); } while (0)
|
|
|
|
/*
|
|
* Functions used by the tab completion in psql normally found in pgport
|
|
*/
|
|
static void *pg_malloc(size_t size)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
|
|
static int pg_strcasecmp(const char *s1, const char *s2)
|
|
{
|
|
#ifdef WIN32
|
|
return _stricmp(s1, s2);
|
|
#else
|
|
return strcasecmp(s1, s2);
|
|
#endif
|
|
}
|
|
|
|
static int pg_strncasecmp(const char *s1, const char *s2, int len)
|
|
{
|
|
#ifdef WIN32
|
|
return _strnicmp(s1, s2, len);
|
|
#else
|
|
return strncasecmp(s1, s2, len);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Return the word (space delimited) before point. Set skip > 0 to
|
|
* skip that many words; e.g. skip=1 finds the word before the
|
|
* previous one. Return value is NULL or a malloc'ed string.
|
|
*
|
|
* Copied directly from psql.
|
|
*/
|
|
static char *
|
|
previous_word(int point, int skip)
|
|
{
|
|
int i,
|
|
start = 0,
|
|
end = -1,
|
|
inquotes = 0;
|
|
char *s;
|
|
|
|
while (skip-- >= 0)
|
|
{
|
|
/* first we look for a space before the current word */
|
|
for (i = point; i >= 0; i--)
|
|
if (rl_line_buffer[i] == ' ')
|
|
break;
|
|
|
|
/* now find the first non-space which then constitutes the end */
|
|
for (; i >= 0; i--)
|
|
if (rl_line_buffer[i] != ' ')
|
|
{
|
|
end = i;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If no end found we return null, because there is no word before the
|
|
* point
|
|
*/
|
|
if (end == -1)
|
|
return NULL;
|
|
|
|
/*
|
|
* Otherwise we now look for the start. The start is either the last
|
|
* character before any space going backwards from the end, or it's
|
|
* simply character 0
|
|
*/
|
|
for (start = end; start > 0; start--)
|
|
{
|
|
if (rl_line_buffer[start] == '"')
|
|
inquotes = !inquotes;
|
|
if ((rl_line_buffer[start - 1] == ' ') && inquotes == 0)
|
|
break;
|
|
}
|
|
|
|
point = start;
|
|
}
|
|
|
|
/* make a copy */
|
|
s = (char *)pg_malloc(end - start + 2);
|
|
|
|
strncpy(s, &rl_line_buffer[start], end - start + 1);
|
|
s[end - start + 1] = '\0';
|
|
|
|
return s;
|
|
}
|
|
|
|
/* Find the parenthesis after the last word */
|
|
/* Copied directly from psql */
|
|
static int find_open_parenthesis(int end)
|
|
{
|
|
int i = end-1;
|
|
|
|
while((rl_line_buffer[i]!=' ')&&(i>=0))
|
|
{
|
|
if (rl_line_buffer[i]=='(') return 1;
|
|
i--;
|
|
}
|
|
while((rl_line_buffer[i]==' ')&&(i>=0))
|
|
{
|
|
i--;
|
|
}
|
|
if (rl_line_buffer[i]=='(')
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* Forward declarations
|
|
*/
|
|
static char *complete_from_list(const char *text, const char * const *list);
|
|
static char *complete_from_const(const char *text, const char *string);
|
|
static char *complete_from_query(const char *text, const char *query, const char *addon, void *dbptr);
|
|
static char *complete_from_schema_query(const char *text, const void* query, const char *addon, void *dbptr);
|
|
static char *complete_create_command(char *text);
|
|
static char *complete_filename();
|
|
|
|
|
|
/*
|
|
* Include the main tab completion functionality from psql
|
|
*/
|
|
#include "tab-complete.inc"
|
|
|
|
|
|
/*
|
|
* Completion functions mimicking those from psql, only returning a single space separated
|
|
* string instead of being called multiple times by the readline library.
|
|
*/
|
|
static char *_complete_from_list(const char *text, const char * const *list, int casesensitive)
|
|
{
|
|
int string_length = strlen(text);
|
|
int size = 0;
|
|
int i;
|
|
char *r;
|
|
|
|
for (i = 0; list[i] != NULL; i++)
|
|
{
|
|
if (casesensitive && strncmp(text, list[i], string_length) == 0)
|
|
size += strlen(list[i]);
|
|
if (!casesensitive && pg_strncasecmp(text, list[i], string_length) == 0)
|
|
size += strlen(list[i]);
|
|
}
|
|
|
|
if (size == 0)
|
|
return NULL;
|
|
|
|
r = calloc(size + i*2 + 1, 1);
|
|
for (i = 0; list[i] != NULL; i++)
|
|
{
|
|
if ((casesensitive && strncmp(text, list[i], string_length) == 0)
|
|
||
|
|
(!casesensitive && pg_strncasecmp(text, list[i], string_length) == 0))
|
|
{
|
|
strcat(r,list[i]);
|
|
strcat(r," \t");
|
|
}
|
|
}
|
|
r[strlen(r)-1] = '\0'; /* Chop of trailing space */
|
|
return r;
|
|
}
|
|
|
|
static char *complete_from_list(const char *text, const char * const *list)
|
|
{
|
|
char *r = _complete_from_list(text, list, 1);
|
|
if (r)
|
|
return r;
|
|
return _complete_from_list(text, list, 0);
|
|
}
|
|
|
|
|
|
static char *complete_from_const(const char *text, const char *string)
|
|
{
|
|
return strdup(string);
|
|
}
|
|
static int lower_identifier(const char *ident, char *out,void *dbptr)
|
|
{
|
|
size_t buflen = strlen(ident) + 1;
|
|
|
|
char *sname;
|
|
char *oname;
|
|
char *optr;
|
|
char *tmp;
|
|
int inquotes=0;
|
|
int isneedlower=0;
|
|
int schemaquoted,objectquoted;
|
|
int encoding=6; // pg_enc.PG_UTF8
|
|
int enc_is_single_byte = 0;
|
|
int utf8len=0;
|
|
/* Initialize, making a certainly-large-enough output buffer */
|
|
schemaquoted = objectquoted = 0;
|
|
/* Scan */
|
|
inquotes = 0;
|
|
optr=out;
|
|
while (*ident)
|
|
{
|
|
unsigned char ch = (unsigned char) *ident++;
|
|
utf8len++;
|
|
if (ch == '"')
|
|
{
|
|
if (inquotes && *ident == '"')
|
|
{
|
|
/* two quote marks within a quoted identifier = emit quote */
|
|
*optr++ = '"';
|
|
ident++;
|
|
utf8len++;
|
|
}
|
|
else
|
|
{
|
|
inquotes = !inquotes;
|
|
objectquoted = 1;
|
|
*optr++ = '"';
|
|
}
|
|
}
|
|
else if (!enc_is_single_byte && ch>127)
|
|
{
|
|
/*
|
|
* Transfer multibyte characters without further processing. They
|
|
* wouldn't be affected by our downcasing rule anyway, and this
|
|
* avoids possibly doing the wrong thing in unsafe client
|
|
* encodings.
|
|
*/
|
|
int chlen = PQmblenBounded(ident - 1, encoding);
|
|
|
|
*optr++ = (char) ch;
|
|
while (--chlen > 0)
|
|
*optr++ = *ident++;
|
|
}
|
|
else
|
|
{
|
|
if (!inquotes)
|
|
{
|
|
/*
|
|
* This downcasing transformation should match the backend's
|
|
* downcase_identifier() as best we can. We do not know the
|
|
* backend's locale, though, so it's necessarily approximate.
|
|
* We assume that psql is operating in the same locale and
|
|
* encoding as the backend.
|
|
*/
|
|
if (ch >= 'A' && ch <= 'Z')
|
|
ch += 'a' - 'A';
|
|
else if (enc_is_single_byte && ch>127 && isupper(ch))
|
|
ch = tolower(ch);
|
|
}
|
|
*optr++ = (char) ch;
|
|
}
|
|
}
|
|
*optr = '\0';
|
|
return utf8len;
|
|
}
|
|
|
|
static char* _complete_from_query(const char* text, const char* query, const SchemaQuery* squery, const char* addon, void* dbptr)
|
|
{
|
|
int string_length = strlen(text);
|
|
char* e_text;
|
|
char* e_text_ident;
|
|
char* complete_query = NULL;
|
|
char* t;
|
|
|
|
e_text_ident = malloc(string_length * 2 + 1);
|
|
PQescapeString(e_text_ident,text, strlen(text));
|
|
string_length=strlen(e_text_ident);
|
|
e_text = malloc(string_length * 2 + 1);
|
|
int utf8len=lower_identifier(e_text_ident,e_text,dbptr);
|
|
string_length=utf8len;
|
|
free(e_text_ident);
|
|
|
|
if (query != NULL)
|
|
{
|
|
/* Normal query */
|
|
int bufsize = 1024;
|
|
char* e_addon;
|
|
|
|
/* Normal query needs escaped string */
|
|
if (addon)
|
|
{
|
|
e_addon = malloc(strlen(addon) * 2 + 1);
|
|
PQescapeString(e_addon, addon, strlen(addon));
|
|
}
|
|
else
|
|
e_addon = strdup("");
|
|
|
|
while (1)
|
|
{
|
|
int r;
|
|
complete_query = realloc(complete_query, bufsize);
|
|
#ifdef WIN32
|
|
r = _snprintf(complete_query, bufsize, query, string_length, e_text, e_addon);
|
|
#else
|
|
r = snprintf(complete_query, bufsize, query, string_length, e_text, e_addon);
|
|
#endif
|
|
if (r < 0 || r >= bufsize)
|
|
bufsize *= 2;
|
|
else
|
|
break;
|
|
}
|
|
free(e_addon);
|
|
}
|
|
else
|
|
{
|
|
/* Schema query */
|
|
char* selcondition = NULL;
|
|
char* viscondition = NULL;
|
|
char* suppress_str = NULL;
|
|
const char* qualresult;
|
|
int bufsize = 2048;
|
|
|
|
if (squery->selcondition)
|
|
{
|
|
selcondition = malloc(strlen(squery->selcondition) + 10);
|
|
sprintf(selcondition, "%s AND ", squery->selcondition);
|
|
}
|
|
else
|
|
selcondition = strdup("");
|
|
|
|
if (squery->viscondition)
|
|
{
|
|
viscondition = malloc(strlen(squery->viscondition) + 10);
|
|
sprintf(viscondition, " AND %s", squery->viscondition);
|
|
}
|
|
else
|
|
viscondition = strdup("");
|
|
|
|
if (strcmp(squery->catname, "pg_catalog.pg_class c") == 0 &&
|
|
strncmp(text, "pg_", 3) != 0)
|
|
suppress_str = " AND c.relnamespace <> (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')";
|
|
else
|
|
suppress_str = "";
|
|
|
|
qualresult = squery->qualresult;
|
|
if (qualresult == NULL)
|
|
qualresult = squery->result;
|
|
|
|
while (1)
|
|
{
|
|
int r;
|
|
complete_query = realloc(complete_query, bufsize);
|
|
#ifdef WIN32
|
|
r = _snprintf(complete_query, bufsize,
|
|
#else
|
|
r = snprintf(complete_query, bufsize,
|
|
#endif
|
|
"SELECT %s FROM %s WHERE %s substring(%s,1,%d)='%s' %s %s "
|
|
"\nUNION\n"
|
|
"SELECT pg_catalog.quote_ident(n.nspname) || '.' FROM pg_catalog.pg_namespace n WHERE pg_catalog.has_schema_privilege(n.oid,'usage') and substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d)='%s' AND (SELECT pg_catalog.count(*) FROM pg_catalog.pg_namespace WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d)= substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1))>1"
|
|
"\nUNION\n"
|
|
"SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s FROM %s, pg_catalog.pg_namespace n WHERE pg_catalog.has_schema_privilege(n.oid,'usage') and %s = n.oid AND %s substring(pg_catalog.quote_ident(n.nspname) || '.' || %s,1,%d)='%s' AND substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d) = substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(n.nspname))+1) AND (SELECT pg_catalog.count(*) FROM pg_catalog.pg_namespace WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) = substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1"
|
|
"\n%s",
|
|
squery->result,
|
|
squery->catname,
|
|
selcondition,
|
|
squery->result,
|
|
string_length,
|
|
e_text,
|
|
viscondition,
|
|
suppress_str,
|
|
string_length,
|
|
e_text,
|
|
string_length,
|
|
e_text,
|
|
qualresult,
|
|
squery->catname,
|
|
squery->namespace,
|
|
selcondition,
|
|
qualresult,
|
|
string_length,
|
|
e_text,
|
|
string_length,
|
|
e_text,
|
|
string_length,
|
|
e_text,
|
|
addon ? addon : "");
|
|
|
|
if (r < 0 || r >= bufsize)
|
|
bufsize *= 2;
|
|
else
|
|
{
|
|
// namespace=='-'
|
|
if (squery->namespace[0] == '-') {
|
|
char* newlinePtr = strchr(complete_query, '\n');
|
|
if (newlinePtr != NULL) *newlinePtr = '\0';
|
|
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
free(viscondition);
|
|
free(selcondition);
|
|
|
|
}
|
|
t = pg_query_to_single_ordered_string(complete_query, dbptr);
|
|
if (complete_query)
|
|
free(complete_query);
|
|
free(e_text);
|
|
return t;
|
|
}
|
|
|
|
static char *complete_from_query(const char *text, const char *query, const char *addon, void *dbptr)
|
|
{
|
|
return _complete_from_query(text, query, NULL, addon, dbptr);
|
|
}
|
|
|
|
static char *complete_from_schema_query(const char *text, const void* query, const char *addon, void *dbptr)
|
|
{
|
|
/* query really is a SchemaQuery, but we don't know about SchemaQuery early enough. */
|
|
return _complete_from_query(text, NULL, (SchemaQuery *)query, addon, dbptr);
|
|
}
|
|
|
|
static char *complete_create_command(char *text)
|
|
{
|
|
int string_length = strlen(text);
|
|
int size = 0;
|
|
int i;
|
|
char *r;
|
|
|
|
for (i = 0; words_after_create[i].name != NULL; i++)
|
|
{
|
|
if (pg_strncasecmp(text, words_after_create[i].name, string_length) == 0)
|
|
size += strlen(words_after_create[i].name);
|
|
}
|
|
if (size == 0)
|
|
return NULL;
|
|
|
|
r = calloc(size + i*2 + 1, 1);
|
|
for (i = 0; words_after_create[i].name != NULL; i++)
|
|
{
|
|
if (pg_strncasecmp(text, words_after_create[i].name, string_length) == 0)
|
|
{
|
|
strcat(r,words_after_create[i].name);
|
|
strcat(r," \t");
|
|
}
|
|
}
|
|
r[strlen(r)-1] = '\0'; /* Chop of trailing space */
|
|
return r;
|
|
}
|
|
|
|
/* Not implemented */
|
|
static char *complete_filename()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Entrypoint from the C++ world
|
|
*/
|
|
char *tab_complete(const char *allstr, const int startptr, const int endptr, void *dbptr)
|
|
{
|
|
rl_line_buffer = (char *)allstr;
|
|
return psql_completion((char *)(allstr + startptr), startptr,endptr,dbptr);
|
|
}
|