aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--erts/doc/src/erl_nif.xml44
-rw-r--r--erts/emulator/beam/erl_nif.c54
-rw-r--r--erts/emulator/beam/erl_nif_api_funcs.h14
-rw-r--r--erts/emulator/drivers/common/inet_drv.c77
-rw-r--r--erts/emulator/sys/win32/sys.c118
-rw-r--r--erts/emulator/test/nif_SUITE.erl35
-rw-r--r--erts/emulator/test/nif_SUITE_data/nif_SUITE.c106
-rw-r--r--erts/emulator/test/port_SUITE.erl13
-rw-r--r--lib/inets/doc/src/http_server.xml13
-rw-r--r--lib/inets/doc/src/notes.xml55
-rw-r--r--lib/inets/src/http_client/httpc_cookie.erl12
-rw-r--r--lib/inets/src/http_lib/http_util.erl106
-rw-r--r--lib/inets/src/http_server/httpd_instance_sup.erl6
-rw-r--r--lib/inets/src/http_server/httpd_sup.erl10
-rw-r--r--lib/inets/src/http_server/httpd_util.erl21
-rw-r--r--lib/inets/src/inets_app/inets.appup.src20
-rw-r--r--lib/inets/src/inets_app/inets.erl2
-rw-r--r--lib/inets/test/http_format_SUITE.erl16
-rw-r--r--lib/inets/vsn.mk17
-rw-r--r--lib/odbc/test/Makefile114
-rw-r--r--lib/odbc/test/README86
-rw-r--r--lib/odbc/test/odbc.dynspec31
-rw-r--r--lib/odbc/test/odbc.spec9
-rw-r--r--lib/odbc/test/odbc.spec.win5
-rw-r--r--lib/odbc/test/odbc_connect_SUITE.erl816
-rw-r--r--lib/odbc/test/odbc_data_type_SUITE.erl1498
-rw-r--r--lib/odbc/test/odbc_query_SUITE.erl1453
-rw-r--r--lib/odbc/test/odbc_start_SUITE.erl147
-rw-r--r--lib/odbc/test/odbc_test.hrl37
-rw-r--r--lib/odbc/test/odbc_test_lib.erl77
-rw-r--r--lib/odbc/test/oracle.erl246
-rw-r--r--lib/odbc/test/postgres.erl294
-rw-r--r--lib/odbc/test/sqlserver.erl298
-rw-r--r--lib/ssl/src/ssl_cipher.erl54
-rw-r--r--lib/ssl/src/ssl_connection.erl14
-rw-r--r--lib/ssl/src/ssl_handshake.erl25
-rw-r--r--lib/ssl/src/ssl_record.erl51
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl20
-rw-r--r--lib/tools/src/xref_base.erl174
-rw-r--r--lib/tools/src/xref_compiler.erl133
-rw-r--r--lib/tools/src/xref_reader.erl52
-rw-r--r--lib/tools/test/xref_SUITE.erl334
-rw-r--r--lib/tools/test/xref_SUITE_data/read/read.erl12
43 files changed, 6196 insertions, 523 deletions
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml
index 5ec844e2ad..03bd42d3b1 100644
--- a/erts/doc/src/erl_nif.xml
+++ b/erts/doc/src/erl_nif.xml
@@ -477,6 +477,12 @@ typedef enum {
<c>term</c> is not an atom with maximum length of
<c>size-1</c>.</p></desc>
</func>
+ <func><name><ret>int</ret><nametext>enif_get_atom_length(ErlNifEnv* env, ERL_NIF_TERM term, unsigned* len)</nametext></name>
+ <fsummary>Get the length of atom <c>term</c>.</fsummary>
+ <desc><p>Set <c>*len</c> to the length (number of bytes excluding
+ terminating null character) of <c>term</c> or return false if
+ <c>term</c> is not an atom.</p></desc>
+ </func>
<func><name><ret>int</ret><nametext>enif_get_double(ErlNifEnv* env, ERL_NIF_TERM term, double* dp)</nametext></name>
<fsummary>Read a floating-point number term.</fsummary>
<desc><p>Set <c>*dp</c> to the floating point value of
@@ -494,6 +500,11 @@ typedef enum {
<c>list</c> or return false if <c>list</c> is not a non-empty
list.</p></desc>
</func>
+ <func><name><ret>int</ret><nametext>enif_get_list_length(ErlNifEnv* env, ERL_NIF_TERM term, unsigned* len)</nametext></name>
+ <fsummary>Get the length of list <c>term</c>.</fsummary>
+ <desc><p>Set <c>*len</c> to the length of <c>term</c> or return
+ false if <c>term</c> is not a list.</p></desc>
+ </func>
<func><name><ret>int</ret><nametext>enif_get_long(ErlNifEnv* env, ERL_NIF_TERM term, long int* ip)</nametext></name>
<fsummary>Read an long integer term.</fsummary>
<desc><p>Set <c>*ip</c> to the long integer value of
@@ -597,10 +608,24 @@ typedef enum {
<fsummary>Determine if a term is a reference</fsummary>
<desc><p>Return true if <c>term</c> is a reference.</p></desc>
</func>
+ <func><name><ret>int</ret><nametext>enif_is_tuple(ErlNifEnv* env, ERL_NIF_TERM term)</nametext></name>
+ <fsummary>Determine if a term is a tuple</fsummary>
+ <desc><p>Return true if <c>term</c> is a tuple.</p></desc>
+ </func>
+ <func><name><ret>int</ret><nametext>enif_is_list(ErlNifEnv* env, ERL_NIF_TERM term)</nametext></name>
+ <fsummary>Determine if a term is a list</fsummary>
+ <desc><p>Return true if <c>term</c> is a list.</p></desc>
+ </func>
<func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_atom(ErlNifEnv* env, const char* name)</nametext></name>
<fsummary>Create an atom term</fsummary>
- <desc><p>Create an atom term from the C-string <c>name</c>. Unlike other terms, atom
- terms may be saved and used between NIF calls.</p></desc>
+ <desc><p>Create an atom term from the null-terminated C-string <c>name</c>.
+ Unlike other terms, atom terms may be saved and used between NIF calls.</p></desc>
+ </func>
+ <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_atom_len(ErlNifEnv* env, const char* name, size_t len)</nametext></name>
+ <fsummary>Create an atom term</fsummary>
+ <desc><p>Create an atom term from the string <c>name</c> with length <c>len</c>.
+ Null-characters are treated as any other characters.
+ Unlike other terms, atom terms may be saved and used between NIF calls.</p></desc>
</func>
<func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_badarg(ErlNifEnv* env)</nametext></name>
<fsummary>Make a badarg exception.</fsummary>
@@ -620,10 +645,17 @@ typedef enum {
<func><name><ret>int</ret><nametext>enif_make_existing_atom(ErlNifEnv* env, const char* name, ERL_NIF_TERM* atom)</nametext></name>
<fsummary>Create an existing atom term</fsummary>
<desc><p>Try to create the term of an already existing atom from
- the C-string <c>name</c>. If the atom already exist store the
+ the null-terminated C-string <c>name</c>. If the atom already exists store the
term in <c>*atom</c> and return true, otherwise return
false.</p></desc>
</func>
+ <func><name><ret>int</ret><nametext>enif_make_existing_atom_len(ErlNifEnv* env, const char* name, size_t len, ERL_NIF_TERM* atom)</nametext></name>
+ <fsummary>Create an existing atom term</fsummary>
+ <desc><p>Try to create the term of an already existing atom from the
+ string <c>name</c> with length <c>len</c>. Null-characters are treated
+ as any other characters. If the atom already exists store the term
+ in <c>*atom</c> and return true, otherwise return false.</p></desc>
+ </func>
<func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_int(ErlNifEnv* env, int i)</nametext></name>
<fsummary>Create an integer term</fsummary>
<desc><p>Create an integer term.</p></desc>
@@ -692,6 +724,12 @@ typedef enum {
<desc><p>Create a list containing the characters of the
null-terminated string <c>string</c> with encoding <seealso marker="#ErlNifCharEncoding">encoding</seealso>.</p></desc>
</func>
+ <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_string_len(ErlNifEnv* env, const char* string, size_t len, ErlNifCharEncoding encoding)</nametext></name>
+ <fsummary>Create a string.</fsummary>
+ <desc><p>Create a list containing the characters of the string <c>string</c> with
+ length <c>len</c> and encoding <seealso marker="#ErlNifCharEncoding">encoding</seealso>.
+ Null-characters are treated as any other characters.</p></desc>
+ </func>
<func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_sub_binary(ErlNifEnv*
env, ERL_NIF_TERM bin_term, unsigned pos, unsigned size)</nametext></name>
<fsummary>Make a subbinary term.</fsummary>
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c
index cee4df72a2..7095ae03e7 100644
--- a/erts/emulator/beam/erl_nif.c
+++ b/erts/emulator/beam/erl_nif.c
@@ -248,6 +248,16 @@ int enif_is_ref(ErlNifEnv* env, ERL_NIF_TERM term)
return is_ref(term);
}
+int enif_is_tuple(ErlNifEnv* env, ERL_NIF_TERM term)
+{
+ return is_tuple(term);
+}
+
+int enif_is_list(ErlNifEnv* env, ERL_NIF_TERM term)
+{
+ return is_list(term) || is_nil(term);
+}
+
static void aligned_binary_dtor(struct enif_tmp_obj_t* obj)
{
erts_free_aligned_binary_bytes_extra((byte*)obj,ERTS_ALC_T_TMP);
@@ -591,6 +601,15 @@ int enif_get_double(ErlNifEnv* env, Eterm term, double* dp)
return 1;
}
+int enif_get_atom_length(ErlNifEnv* env, Eterm atom, unsigned* len)
+{
+ Atom* ap;
+ if (is_not_atom(atom)) return 0;
+ ap = atom_tab(atom_val(atom));
+ *len = ap->len;
+ return 1;
+}
+
int enif_get_list_cell(ErlNifEnv* env, Eterm term, Eterm* head, Eterm* tail)
{
Eterm* val;
@@ -601,6 +620,13 @@ int enif_get_list_cell(ErlNifEnv* env, Eterm term, Eterm* head, Eterm* tail)
return 1;
}
+int enif_get_list_length(ErlNifEnv* env, Eterm term, unsigned* len)
+{
+ if (is_not_list(term) && is_not_nil(term)) return 0;
+ *len = list_length(term);
+ return 1;
+}
+
ERL_NIF_TERM enif_make_int(ErlNifEnv* env, int i)
{
#if SIZEOF_INT == ERTS_SIZEOF_ETERM
@@ -640,12 +666,23 @@ ERL_NIF_TERM enif_make_double(ErlNifEnv* env, double d)
ERL_NIF_TERM enif_make_atom(ErlNifEnv* env, const char* name)
{
- return am_atom_put(name, sys_strlen(name));
+ return enif_make_atom_len(env, name, sys_strlen(name));
+}
+
+ERL_NIF_TERM enif_make_atom_len(ErlNifEnv* env, const char* name, size_t len)
+{
+ return am_atom_put(name, len);
}
int enif_make_existing_atom(ErlNifEnv* env, const char* name, ERL_NIF_TERM* atom)
{
- return erts_atom_get(name, sys_strlen(name), atom);
+ return enif_make_existing_atom_len(env, name, sys_strlen(name), atom);
+}
+
+int enif_make_existing_atom_len(ErlNifEnv* env, const char* name, size_t len,
+ ERL_NIF_TERM* atom)
+{
+ return erts_atom_get(name, len, atom);
}
ERL_NIF_TERM enif_make_tuple(ErlNifEnv* env, unsigned cnt, ...)
@@ -724,11 +761,16 @@ ERL_NIF_TERM enif_make_list_from_array(ErlNifEnv* env, const ERL_NIF_TERM arr[],
ERL_NIF_TERM enif_make_string(ErlNifEnv* env, const char* string,
ErlNifCharEncoding encoding)
-{
- Sint n = sys_strlen(string);
- Eterm* hp = alloc_heap(env,n*2);
+{
+ return enif_make_string_len(env, string, sys_strlen(string), encoding);
+}
+
+ERL_NIF_TERM enif_make_string_len(ErlNifEnv* env, const char* string,
+ size_t len, ErlNifCharEncoding encoding)
+{
+ Eterm* hp = alloc_heap(env,len*2);
ASSERT(encoding == ERL_NIF_LATIN1);
- return erts_bld_string_n(&hp,NULL,string,n);
+ return erts_bld_string_n(&hp,NULL,string,len);
}
ERL_NIF_TERM enif_make_ref(ErlNifEnv* env)
diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h
index fe8d2664e1..44bcca9ca4 100644
--- a/erts/emulator/beam/erl_nif_api_funcs.h
+++ b/erts/emulator/beam/erl_nif_api_funcs.h
@@ -106,6 +106,13 @@ ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_resource,(ErlNifEnv*, void* obj));
ERL_NIF_API_FUNC_DECL(int,enif_get_resource,(ErlNifEnv*, ERL_NIF_TERM term, ErlNifResourceType* type, void** objp));
ERL_NIF_API_FUNC_DECL(unsigned,enif_sizeof_resource,(ErlNifEnv*, void* obj));
ERL_NIF_API_FUNC_DECL(unsigned char*,enif_make_new_binary,(ErlNifEnv*,unsigned size,ERL_NIF_TERM* termp));
+ERL_NIF_API_FUNC_DECL(int,enif_is_list,(ErlNifEnv*, ERL_NIF_TERM term));
+ERL_NIF_API_FUNC_DECL(int,enif_is_tuple,(ErlNifEnv*, ERL_NIF_TERM term));
+ERL_NIF_API_FUNC_DECL(int,enif_get_atom_length,(ErlNifEnv*, ERL_NIF_TERM atom, unsigned* len));
+ERL_NIF_API_FUNC_DECL(int,enif_get_list_length,(ErlNifEnv* env, ERL_NIF_TERM term, unsigned* len));
+ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM, enif_make_atom_len,(ErlNifEnv* env, const char* name, size_t len));
+ERL_NIF_API_FUNC_DECL(int, enif_make_existing_atom_len,(ErlNifEnv* env, const char* name, size_t len, ERL_NIF_TERM* atom));
+ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_string_len,(ErlNifEnv* env, const char* string, size_t len, ErlNifCharEncoding));
/*
** Add last to keep compatibility on Windows!!!
@@ -198,6 +205,13 @@ ERL_NIF_API_FUNC_DECL(unsigned char*,enif_make_new_binary,(ErlNifEnv*,unsigned s
# define enif_get_resource ERL_NIF_API_FUNC_MACRO(enif_get_resource)
# define enif_sizeof_resource ERL_NIF_API_FUNC_MACRO(enif_sizeof_resource)
# define enif_make_new_binary ERL_NIF_API_FUNC_MACRO(enif_make_new_binary)
+# define enif_is_list ERL_NIF_API_FUNC_MACRO(enif_is_list)
+# define enif_is_tuple ERL_NIF_API_FUNC_MACRO(enif_is_tuple)
+# define enif_get_atom_length ERL_NIF_API_FUNC_MACRO(enif_get_atom_length)
+# define enif_get_list_length ERL_NIF_API_FUNC_MACRO(enif_get_list_length)
+# define enif_make_atom_len ERL_NIF_API_FUNC_MACRO(enif_make_atom_len)
+# define enif_make_existing_atom_len ERL_NIF_API_FUNC_MACRO(enif_make_existing_atom_len)
+# define enif_make_string_len ERL_NIF_API_FUNC_MACRO(enif_make_string_len)
#endif
#ifndef enif_make_list1
diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c
index e8456cc616..87691fc1bc 100644
--- a/erts/emulator/drivers/common/inet_drv.c
+++ b/erts/emulator/drivers/common/inet_drv.c
@@ -55,6 +55,21 @@
#include "erl_driver.h"
+/* The IS_SOCKET_ERROR macro below is used for portability reasons. While
+ POSIX specifies that errors from socket-related system calls should be
+ indicated with a -1 return value, some users have experienced non-Windows
+ OS kernels that return negative values other than -1. While one can argue
+ that such kernels are technically broken, comparing against values less
+ than 0 covers their out-of-spec return values without imposing incorrect
+ semantics on systems that manage to correctly return -1 for errors, thus
+ increasing Erlang's portability.
+*/
+#ifdef __WIN32__
+#define IS_SOCKET_ERROR(val) ((val) == SOCKET_ERROR)
+#else
+#define IS_SOCKET_ERROR(val) ((val) < 0)
+#endif
+
#ifdef __WIN32__
#define STRNCASECMP strncasecmp
@@ -279,7 +294,7 @@ static int (*p_sctp_bindx)(int sd, struct sockaddr *addrs,
#define DEBUGF(X) printf X
#endif
-#if !defined(__WIN32__) && !defined(HAVE_STRNCASECMP)
+#if !defined(HAVE_STRNCASECMP)
#define STRNCASECMP my_strncasecmp
static int my_strncasecmp(const char *s1, const char *s2, size_t n)
@@ -299,6 +314,7 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n)
#define INVALID_SOCKET -1
#define INVALID_EVENT -1
#define SOCKET_ERROR -1
+
#define SOCKET int
#define HANDLE long int
#define FD_READ ERL_DRV_READ
@@ -3684,7 +3700,7 @@ static int inet_ctl_fdopen(inet_descriptor* desc, int domain, int type,
unsigned int sz = sizeof(name);
/* check that it is a socket and that the socket is bound */
- if (sock_name(s, (struct sockaddr*) &name, &sz) == SOCKET_ERROR)
+ if (IS_SOCKET_ERROR(sock_name(s, (struct sockaddr*) &name, &sz)))
return ctl_error(sock_errno(), rbuf, rsize);
desc->s = s;
if ((desc->event = sock_create_event(desc)) == INVALID_EVENT)
@@ -3696,7 +3712,7 @@ static int inet_ctl_fdopen(inet_descriptor* desc, int domain, int type,
desc->state = INET_STATE_BOUND; /* assume bound */
if (type == SOCK_STREAM) { /* check if connected */
sz = sizeof(name);
- if (sock_peer(s, (struct sockaddr*) &name, &sz) != SOCKET_ERROR)
+ if (!IS_SOCKET_ERROR(sock_peer(s, (struct sockaddr*) &name, &sz)))
desc->state = INET_STATE_CONNECTED;
}
@@ -5627,8 +5643,8 @@ static int inet_fill_opts(inet_descriptor* desc,
buf += arg_sz;
len -= arg_sz;
}
- if (sock_getopt(desc->s,proto,type,arg_ptr,&arg_sz) ==
- SOCKET_ERROR) {
+ if (IS_SOCKET_ERROR(sock_getopt(desc->s,proto,type,
+ arg_ptr,&arg_sz))) {
TRUNCATE_TO(0,ptr);
continue;
}
@@ -5645,7 +5661,7 @@ static int inet_fill_opts(inet_descriptor* desc,
RETURN_ERROR();
}
/* We have 5 bytes allocated to ptr */
- if (sock_getopt(desc->s,proto,type,arg_ptr,&arg_sz) == SOCKET_ERROR) {
+ if (IS_SOCKET_ERROR(sock_getopt(desc->s,proto,type,arg_ptr,&arg_sz))) {
TRUNCATE_TO(0,ptr);
continue;
}
@@ -6711,7 +6727,7 @@ static int inet_ctl(inet_descriptor* desc, int cmd, char* buf, int len,
if (len != 0)
return ctl_error(EINVAL, rbuf, rsize);
- if (sock_hostname(tbuf, MAXHOSTNAMELEN) == SOCKET_ERROR)
+ if (IS_SOCKET_ERROR(sock_hostname(tbuf, MAXHOSTNAMELEN)))
return ctl_error(sock_errno(), rbuf, rsize);
return ctl_reply(INET_REP_OK, tbuf, strlen(tbuf), rbuf, rsize);
}
@@ -6728,7 +6744,7 @@ static int inet_ctl(inet_descriptor* desc, int cmd, char* buf, int len,
return ctl_error(ENOTCONN, rbuf, rsize);
if ((ptr = desc->peer_ptr) == NULL) {
ptr = &peer;
- if (sock_peer(desc->s, (struct sockaddr*)ptr,&sz) == SOCKET_ERROR)
+ if (IS_SOCKET_ERROR(sock_peer(desc->s, (struct sockaddr*)ptr,&sz)))
return ctl_error(sock_errno(), rbuf, rsize);
}
if (inet_get_address(desc->sfamily, tbuf, ptr, &sz) < 0)
@@ -6765,7 +6781,7 @@ static int inet_ctl(inet_descriptor* desc, int cmd, char* buf, int len,
if ((ptr = desc->name_ptr) == NULL) {
ptr = &name;
- if (sock_name(desc->s, (struct sockaddr*)ptr, &sz) == SOCKET_ERROR)
+ if (IS_SOCKET_ERROR(sock_name(desc->s, (struct sockaddr*)ptr, &sz)))
return ctl_error(sock_errno(), rbuf, rsize);
}
if (inet_get_address(desc->sfamily, tbuf, ptr, &sz) < 0)
@@ -6804,7 +6820,7 @@ static int inet_ctl(inet_descriptor* desc, int cmd, char* buf, int len,
if (inet_set_address(desc->sfamily, &local, buf, &len) == NULL)
return ctl_error(EINVAL, rbuf, rsize);
- if (sock_bind(desc->s,(struct sockaddr*) &local, len) == SOCKET_ERROR)
+ if (IS_SOCKET_ERROR(sock_bind(desc->s,(struct sockaddr*) &local, len)))
return ctl_error(sock_errno(), rbuf, rsize);
desc->state = INET_STATE_BOUND;
@@ -7237,7 +7253,7 @@ static int tcp_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, int len,
if (len != 2)
return ctl_error(EINVAL, rbuf, rsize);
backlog = get_int16(buf);
- if (sock_listen(desc->inet.s, backlog) == SOCKET_ERROR)
+ if (IS_SOCKET_ERROR(sock_listen(desc->inet.s, backlog)))
return ctl_error(sock_errno(), rbuf, rsize);
desc->inet.state = TCP_STATE_LISTEN;
return ctl_reply(INET_REP_OK, NULL, 0, rbuf, rsize);
@@ -7271,7 +7287,7 @@ static int tcp_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, int len,
code = sock_connect(desc->inet.s,
(struct sockaddr*) &desc->inet.remote, len);
- if ((code == SOCKET_ERROR) &&
+ if (IS_SOCKET_ERROR(code) &&
((sock_errno() == ERRNO_BLOCK) || /* Winsock2 */
(sock_errno() == EINPROGRESS))) { /* Unix & OSE!! */
sock_select(INETP(desc), FD_CONNECT, 1);
@@ -7947,7 +7963,7 @@ static int tcp_recv(tcp_descriptor* desc, int request_len)
n = sock_recv(desc->inet.s, desc->i_ptr, nread, 0);
- if (n == SOCKET_ERROR) {
+ if (IS_SOCKET_ERROR(n)) {
int err = sock_errno();
if (err == ECONNRESET) {
DEBUGF((" => detected close (connreset)\r\n"));
@@ -8449,8 +8465,8 @@ static int tcp_sendv(tcp_descriptor* desc, ErlIOVec* ev)
(long)desc->inet.port, desc->inet.s, h_len, len));
if (desc->tcp_add_flags & TCP_ADDF_DELAY_SEND) {
n = 0;
- } else if (sock_sendv(desc->inet.s, ev->iov, vsize, &n, 0)
- == SOCKET_ERROR) {
+ } else if (IS_SOCKET_ERROR(sock_sendv(desc->inet.s, ev->iov,
+ vsize, &n, 0))) {
if ((sock_errno() != ERRNO_BLOCK) && (sock_errno() != EINTR)) {
int err = sock_errno();
DEBUGF(("tcp_sendv(%ld): s=%d, "
@@ -8543,7 +8559,7 @@ static int tcp_send(tcp_descriptor* desc, char* ptr, int len)
if (desc->tcp_add_flags & TCP_ADDF_DELAY_SEND) {
sock_send(desc->inet.s, buf, 0, 0);
n = 0;
- } else if (sock_sendv(desc->inet.s,iov,2,&n,0) == SOCKET_ERROR) {
+ } else if (IS_SOCKET_ERROR(sock_sendv(desc->inet.s,iov,2,&n,0))) {
if ((sock_errno() != ERRNO_BLOCK) && (sock_errno() != EINTR)) {
int err = sock_errno();
DEBUGF(("tcp_send(%ld): s=%d,sock_sendv(size=2) errno = %d\r\n",
@@ -8616,7 +8632,7 @@ static int tcp_inet_output(tcp_descriptor* desc, HANDLE event)
int code = sock_peer(desc->inet.s,
(struct sockaddr*) &desc->inet.remote, &sz);
- if (code == SOCKET_ERROR) {
+ if (IS_SOCKET_ERROR(code)) {
desc->inet.state = TCP_STATE_BOUND; /* restore state */
ret = async_error(INETP(desc), sock_errno());
goto done;
@@ -8657,7 +8673,7 @@ static int tcp_inet_output(tcp_descriptor* desc, HANDLE event)
vsize = vsize > MAX_VSIZE ? MAX_VSIZE : vsize;
DEBUGF(("tcp_inet_output(%ld): s=%d, About to send %d items\r\n",
(long)desc->inet.port, desc->inet.s, vsize));
- if (sock_sendv(desc->inet.s, iov, vsize, &n, 0)==SOCKET_ERROR) {
+ if (IS_SOCKET_ERROR(sock_sendv(desc->inet.s, iov, vsize, &n, 0))) {
if ((sock_errno() != ERRNO_BLOCK) && (sock_errno() != EINTR)) {
DEBUGF(("tcp_inet_output(%ld): sock_sendv(%d) errno = %d\r\n",
(long)desc->inet.port, vsize, sock_errno()));
@@ -8926,7 +8942,7 @@ static int packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, int len,
sock_select(desc, FD_CONNECT, 1);
code = sock_connect(desc->s, &remote.sa, len);
- if ((code == SOCKET_ERROR) && (sock_errno() == EINPROGRESS)) {
+ if (IS_SOCKET_ERROR(code) && (sock_errno() == EINPROGRESS)) {
/* XXX: Unix only -- WinSock would have a different cond! */
desc->state = SCTP_STATE_CONNECTING;
if (timeout != INET_INFINITY)
@@ -8966,7 +8982,7 @@ static int packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, int len,
code = sock_connect(desc->s,
(struct sockaddr*) &desc->remote, len);
- if (code == SOCKET_ERROR) {
+ if (IS_SOCKET_ERROR(code)) {
sock_connect(desc->s, (struct sockaddr*) NULL, 0);
desc->state &= ~INET_F_ACTIVE;
return ctl_error(sock_errno(), rbuf, rsize);
@@ -9000,7 +9016,7 @@ static int packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, int len,
return ctl_error(EINVAL, rbuf, rsize);
flag = get_int8(buf);
- if (sock_listen(desc->s, flag) == SOCKET_ERROR)
+ if (IS_SOCKET_ERROR(sock_listen(desc->s, flag)))
return ctl_error(sock_errno(), rbuf, rsize);
desc->state = SCTP_STATE_LISTEN; /* XXX: not used? */
@@ -9205,7 +9221,7 @@ static void packet_inet_command(ErlDrvData e, char* buf, int len)
check_result_code:
/* "code" analysis is the same for both SCTP and UDP cases above: */
#endif
- if (code == SOCKET_ERROR) {
+ if (IS_SOCKET_ERROR(code)) {
int err = sock_errno();
inet_reply_error(desc, err);
}
@@ -9304,7 +9320,7 @@ static int packet_inet_input(udp_descriptor* udesc, HANDLE event)
check_result:
#endif
/* Analyse the result: */
- if (n == SOCKET_ERROR
+ if (IS_SOCKET_ERROR(n)
#ifdef HAVE_SCTP
|| (short_recv = (IS_SCTP(desc) && !(mhdr.msg_flags & MSG_EOR)))
/* NB: here we check for EOR not being set -- this is an error as
@@ -9419,7 +9435,7 @@ static int packet_inet_output(udp_descriptor* udesc, HANDLE event)
int code = sock_peer(desc->s,
(struct sockaddr*) &desc->remote, &sz);
- if (code == SOCKET_ERROR) {
+ if (IS_SOCKET_ERROR(code)) {
desc->state = PACKET_STATE_BOUND; /* restore state */
ret = async_error(desc, sock_errno());
goto done;
@@ -9860,23 +9876,26 @@ int erts_sock_connect(erts_sock_t socket, byte *ip_addr, int len, Uint16 port)
if (!inet_set_address(AF_INET, &addr, buf, &blen))
return 0;
- if (SOCKET_ERROR == sock_connect(s,
+ if (IS_SOCKET_ERROR(sock_connect(s,
(struct sockaddr *) &addr,
- sizeof(struct sockaddr_in)))
+ sizeof(struct sockaddr_in))))
return 0;
return 1;
}
Sint erts_sock_send(erts_sock_t socket, const void *buf, Sint len)
{
- return (Sint) sock_send((SOCKET) socket, buf, (size_t) len, 0);
+ Sint result = (Sint) sock_send((SOCKET) socket, buf, (size_t) len, 0);
+ if (IS_SOCKET_ERROR(result))
+ return SOCKET_ERROR;
+ return result;
}
int erts_sock_gethostname(char *buf, int bufsz)
{
- if (sock_hostname(buf, bufsz) == SOCKET_ERROR)
- return -1;
+ if (IS_SOCKET_ERROR(sock_hostname(buf, bufsz)))
+ return SOCKET_ERROR;
return 0;
}
diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c
index 46dee826f0..65719a91d6 100644
--- a/erts/emulator/sys/win32/sys.c
+++ b/erts/emulator/sys/win32/sys.c
@@ -33,6 +33,7 @@
#include "../../drivers/win32/win_con.h"
+
void erts_sys_init_float(void);
void erl_start(int, char**);
@@ -95,6 +96,10 @@ static erts_smp_mtx_t sys_driver_data_lock;
static FUNCTION(int, driver_write, (long, HANDLE, byte*, int));
static void common_stop(int);
static int create_file_thread(struct async_io* aio, int mode);
+#ifdef ERTS_SMP
+static void close_active_handles(ErlDrvPort, const HANDLE* handles, int cnt);
+static DWORD WINAPI threaded_handle_closer(LPVOID param);
+#endif
static DWORD WINAPI threaded_reader(LPVOID param);
static DWORD WINAPI threaded_writer(LPVOID param);
static DWORD WINAPI threaded_exiter(LPVOID param);
@@ -132,6 +137,9 @@ static BOOL win_console = FALSE;
static OSVERSIONINFO int_os_version; /* Version information for Win32. */
+#ifdef ERTS_SMP
+static BOOL (WINAPI *fpCancelIoEx)(HANDLE,LPOVERLAPPED);
+#endif
/* This is the system's main function (which may or may not be called "main")
- do general system-dependent initialization
@@ -676,25 +684,50 @@ release_driver_data(DriverData* dp)
erts_smp_mtx_lock(&sys_driver_data_lock);
#ifdef ERTS_SMP
- /* This is a workaround for the fact that CancelIo cant cancel
- requests issued by another thread and that we still cant use
- CancelIoEx as that's only availabele in Vista etc. */
- if(dp->in.async_io_active && dp->in.fd != INVALID_HANDLE_VALUE) {
- CloseHandle(dp->in.fd);
- dp->in.fd = INVALID_HANDLE_VALUE;
- DEBUGF(("Waiting for the in event thingie"));
- WaitForSingleObject(dp->in.ov.hEvent,INFINITE);
- DEBUGF(("...done\n"));
- }
- if(dp->out.async_io_active && dp->out.fd != INVALID_HANDLE_VALUE) {
- CloseHandle(dp->out.fd);
- dp->out.fd = INVALID_HANDLE_VALUE;
- DEBUGF(("Waiting for the out event thingie"));
- WaitForSingleObject(dp->out.ov.hEvent,INFINITE);
- DEBUGF(("...done\n"));
+ if (fpCancelIoEx != NULL) {
+ if (dp->in.thread == (HANDLE) -1 && dp->in.fd != INVALID_HANDLE_VALUE) {
+ (*fpCancelIoEx)(dp->in.fd, NULL);
+ }
+ if (dp->out.thread == (HANDLE) -1 && dp->out.fd != INVALID_HANDLE_VALUE) {
+ (*fpCancelIoEx)(dp->out.fd, NULL);
+ }
+ }
+ else {
+ /* This is a workaround for the fact that CancelIo cant cancel
+ requests issued by another thread and that we cant use
+ CancelIoEx as that's only availabele in Vista etc.
+ R14: Avoid scheduler deadlock by only wait for 10ms, and then spawn
+ a thread that will keep waiting in in order to close handles. */
+ HANDLE handles[2];
+ int i = 0;
+ int timeout = 10;
+ if(dp->in.async_io_active && dp->in.fd != INVALID_HANDLE_VALUE) {
+ CloseHandle(dp->in.fd);
+ dp->in.fd = INVALID_HANDLE_VALUE;
+ DEBUGF(("Waiting for the in event thingie"));
+ if (WaitForSingleObject(dp->in.ov.hEvent,timeout) == WAIT_TIMEOUT) {
+ handles[i++] = dp->in.ov.hEvent;
+ dp->in.ov.hEvent = NULL;
+ timeout = 0;
+ }
+ DEBUGF(("...done\n"));
+ }
+ if(dp->out.async_io_active && dp->out.fd != INVALID_HANDLE_VALUE) {
+ CloseHandle(dp->out.fd);
+ dp->out.fd = INVALID_HANDLE_VALUE;
+ DEBUGF(("Waiting for the out event thingie"));
+ if (WaitForSingleObject(dp->out.ov.hEvent,timeout) == WAIT_TIMEOUT) {
+ handles[i++] = dp->out.ov.hEvent;
+ dp->out.ov.hEvent = NULL;
+ }
+ DEBUGF(("...done\n"));
+ }
+ if (i > 0) {
+ close_active_handles(dp->port_num, handles, i);
+ }
}
#else
- if (dp->out.thread == (HANDLE) -1 && dp->in.fd != INVALID_HANDLE_VALUE) {
+ if (dp->in.thread == (HANDLE) -1 && dp->in.fd != INVALID_HANDLE_VALUE) {
CancelIo(dp->in.fd);
}
if (dp->out.thread == (HANDLE) -1 && dp->out.fd != INVALID_HANDLE_VALUE) {
@@ -737,6 +770,48 @@ release_driver_data(DriverData* dp)
erts_smp_mtx_unlock(&sys_driver_data_lock);
}
+#ifdef ERTS_SMP
+
+struct handles_to_be_closed
+{
+ int cnt;
+ HANDLE handles[2];
+};
+
+static void close_active_handles(ErlDrvPort port_num, const HANDLE* handles, int cnt)
+{
+ DWORD tid;
+ HANDLE thread;
+ int i;
+ struct handles_to_be_closed* htbc = erts_alloc(ERTS_ALC_T_DRV_TAB,
+ sizeof(struct handles_to_be_closed));
+ htbc->cnt = cnt;
+ for (i=0; i < cnt; ++i) {
+ htbc->handles[i] = handles[i];
+ (void) driver_select(port_num, (ErlDrvEvent)handles[i],
+ ERL_DRV_USE_NO_CALLBACK, 0);
+ }
+ thread = (HANDLE *) _beginthreadex(NULL, 0, threaded_handle_closer, htbc, 0, &tid);
+ CloseHandle(thread);
+}
+
+
+static DWORD WINAPI
+threaded_handle_closer(LPVOID param)
+{
+ struct handles_to_be_closed* htbc = (struct handles_to_be_closed*) param;
+ int i;
+ DEBUGF(("threaded_handle_closer waiting for %d handles\r\n",htbc->cnt));
+ WaitForMultipleObjects(htbc->cnt, htbc->handles, TRUE, INFINITE);
+ for (i=0; i < htbc->cnt; ++i) {
+ CloseHandle(htbc->handles[i]);
+ }
+ erts_free(ERTS_ALC_T_DRV_TAB, htbc);
+ DEBUGF(("threaded_handle_closer terminating\r\n"));
+ return 0;
+}
+#endif /* ERTS_SMP */
+
/*
* Stores input and output file descriptors in the DriverData structure,
* and calls driver_select().
@@ -1026,12 +1101,19 @@ static int
spawn_init()
{
int i;
-
+#ifdef ERTS_SMP
+ HMODULE module = GetModuleHandle("kernel32");
+ fpCancelIoEx = (module != NULL) ?
+ (BOOL (WINAPI *)(HANDLE,LPOVERLAPPED))
+ GetProcAddress(module,"CancelIoEx") : NULL;
+ DEBUGF(("fpCancelIoEx = %p\r\n", fpCancelIoEx));
+#endif
driver_data = (struct driver_data *)
erts_alloc(ERTS_ALC_T_DRV_TAB, max_files * sizeof(struct driver_data));
erts_smp_atomic_add(&sys_misc_mem_sz, max_files*sizeof(struct driver_data));
for (i = 0; i < max_files; i++)
driver_data[i].port_num = PORT_FREE;
+
return 0;
}
diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl
index 522caec8f1..161e38c68a 100644
--- a/erts/emulator/test/nif_SUITE.erl
+++ b/erts/emulator/test/nif_SUITE.erl
@@ -28,7 +28,7 @@
-export([all/1, fin_per_testcase/2, basic/1, reload/1, upgrade/1, heap_frag/1,
types/1, many_args/1, binaries/1, get_string/1, get_atom/1, api_macros/1,
from_array/1, iolist_as_binary/1, resource/1, resource_takeover/1,
- threading/1, neg/1]).
+ threading/1, neg/1, is_checks/1, get_length/1, make_atom/1, make_string/1]).
-export([many_args_100/100]).
-define(nif_stub,nif_stub_error(?LINE)).
@@ -36,7 +36,8 @@
all(suite) ->
[basic, reload, upgrade, heap_frag, types, many_args, binaries, get_string,
get_atom, api_macros, from_array, iolist_as_binary, resource,
- resource_takeover, threading, neg].
+ resource_takeover, threading, neg, is_checks, get_length, make_atom,
+ make_string].
%%init_per_testcase(_Case, Config) ->
%% ?line Dog = ?t:timetrap(?t:seconds(60*60*24)),
@@ -759,7 +760,17 @@ neg(Config) when is_list(Config) ->
?line verify_tmpmem(TmpMem),
?line ok.
+is_checks(doc) -> ["Test all enif_is functions"];
+is_checks(Config) when is_list(Config) ->
+ ?line ensure_lib_loaded(Config, 1),
+ ?line ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end,
+ self(), hd(erlang:ports()), [], [1,9,9,8],
+ {hejsan, "hejsan", [$h,"ejs",<<"an">>]}).
+get_length(doc) -> ["Test all enif_get_length functions"];
+get_length(Config) when is_list(Config) ->
+ ?line ensure_lib_loaded(Config, 1),
+ ?line ok = length_test(hejsan, "hejsan", [], [], not_a_list).
ensure_lib_loaded(Config) ->
ensure_lib_loaded(Config, 1).
@@ -773,6 +784,22 @@ ensure_lib_loaded(Config, Ver) ->
ok
end.
+make_atom(Config) when is_list(Config) ->
+ ?line ensure_lib_loaded(Config, 1),
+ An0Atom = an0atom,
+ An0Atom0 = 'an\000atom\000',
+ ?line Atoms = make_atoms(),
+ ?line 7 = size(Atoms),
+ ?line Atoms = {An0Atom,An0Atom,An0Atom,An0Atom0,An0Atom,An0Atom,An0Atom0}.
+
+make_string(Config) when is_list(Config) ->
+ ?line ensure_lib_loaded(Config, 1),
+ ?line Strings = make_strings(),
+ ?line 4 = size(Strings),
+ A0String = "a0string",
+ A0String0 = [$a,0,$s,$t,$r,$i,$n,$g,0],
+ ?line Strings = {A0String,A0String,A0String,A0String0}.
+
tmpmem() ->
case erlang:system_info({allocator,temp_alloc}) of
false -> undefined;
@@ -855,6 +882,10 @@ get_resource(_,_) -> ?nif_stub.
release_resource(_) -> ?nif_stub.
last_resource_dtor_call() -> ?nif_stub.
make_new_resource(_,_) -> ?nif_stub.
+check_is(_,_,_,_,_,_,_,_,_,_) -> ?nif_stub.
+length_test(_,_,_,_,_) -> ?nif_stub.
+make_atoms() -> ?nif_stub.
+make_strings() -> ?nif_stub.
nif_stub_error(Line) ->
exit({nif_not_loaded,module,?MODULE,line,Line}).
diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
index 3ad4f93374..73226a09cb 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
+++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
@@ -630,6 +630,106 @@ static ERL_NIF_TERM release_resource(ErlNifEnv* env, int argc, const ERL_NIF_TER
return enif_make_atom(env,"ok");
}
+/*
+ * argv[0] an atom
+ * argv[1] a binary
+ * argv[2] a ref
+ * argv[3] 'ok'
+ * argv[4] a fun
+ * argv[5] a pid
+ * argv[6] a port
+ * argv[7] an empty list
+ * argv[8] a non-empty list
+ * argv[9] a tuple
+ */
+static ERL_NIF_TERM check_is(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ERL_NIF_TERM ok_atom = enif_make_atom(env, "ok");
+
+ if (!enif_is_atom(env, argv[0])) return enif_make_badarg(env);
+ if (!enif_is_binary(env, argv[1])) return enif_make_badarg(env);
+ if (!enif_is_ref(env, argv[2])) return enif_make_badarg(env);
+ if (!enif_is_identical(env, argv[3], ok_atom)) return enif_make_badarg(env);
+ if (!enif_is_fun(env, argv[4])) return enif_make_badarg(env);
+ if (!enif_is_pid(env, argv[5])) return enif_make_badarg(env);
+ if (!enif_is_port(env, argv[6])) return enif_make_badarg(env);
+ if (!enif_is_empty_list(env, argv[7])) return enif_make_badarg(env);
+ if (!enif_is_list(env, argv[7])) return enif_make_badarg(env);
+ if (!enif_is_list(env, argv[8])) return enif_make_badarg(env);
+ if (!enif_is_tuple(env, argv[9])) return enif_make_badarg(env);
+
+ return ok_atom;
+}
+
+/*
+ * argv[0] atom with length of 6
+ * argv[1] list with length of 6
+ * argv[2] empty list
+ * argv[3] not an atom
+ * argv[4] not a list
+ */
+static ERL_NIF_TERM length_test(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ unsigned len;
+
+ if (!enif_get_atom_length(env, argv[0], &len) || len != 6)
+ return enif_make_badarg(env);
+
+ if (!enif_get_list_length(env, argv[1], &len) || len != 6)
+ return enif_make_badarg(env);
+
+ if (!enif_get_list_length(env, argv[2], &len) || len != 0)
+ return enif_make_badarg(env);
+
+ if (enif_get_atom_length(env, argv[3], &len))
+ return enif_make_badarg(env);
+
+ if (enif_get_list_length(env, argv[4], &len))
+ return enif_make_badarg(env);
+
+ return enif_make_atom(env, "ok");
+}
+
+static ERL_NIF_TERM make_atoms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ERL_NIF_TERM arr[7];
+ ERL_NIF_TERM existingatom0a, existingatom0b;
+ ERL_NIF_TERM existing0atom0;
+ const char * const an0atom = "an0atom";
+ const char an0atom0[8] = {'a','n','\0','a','t','o','m',0};
+
+ arr[0] = enif_make_atom(env, "an0atom");
+ arr[1] = enif_make_atom_len(env, "an0atom", 7);
+ arr[2] = enif_make_atom_len(env, an0atom, 7);
+ arr[3] = enif_make_atom_len(env, an0atom0, 8);
+
+ if (!enif_make_existing_atom(env, "an0atom", &existingatom0a))
+ return enif_make_atom(env, "error");
+ arr[4] = existingatom0a;
+
+ if (!enif_make_existing_atom_len(env, an0atom, 7, &existingatom0b))
+ return enif_make_atom(env, "error");
+ arr[5] = existingatom0b;
+
+ if (!enif_make_existing_atom_len(env, an0atom0, 8, &existing0atom0))
+ return enif_make_atom(env, "error");
+ arr[6] = existing0atom0;
+
+ return enif_make_tuple7(env,
+ arr[0],arr[1],arr[2],arr[3],arr[4],arr[5],arr[6]);
+}
+
+static ERL_NIF_TERM make_strings(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ const char a0string[8] = {'a','0','s','t','r','i','n','g'};
+ const char a0string0[9] = {'a','\0','s','t','r','i','n','g',0};
+
+ return enif_make_tuple4(env,
+ enif_make_string(env, "a0string", ERL_NIF_LATIN1),
+ enif_make_string_len(env, "a0string", 8, ERL_NIF_LATIN1),
+ enif_make_string_len(env, a0string, 8, ERL_NIF_LATIN1),
+ enif_make_string_len(env, a0string0, 9, ERL_NIF_LATIN1));
+}
static ErlNifFunc nif_funcs[] =
{
@@ -656,7 +756,11 @@ static ErlNifFunc nif_funcs[] =
{"get_resource", 2, get_resource},
{"release_resource", 1, release_resource},
{"last_resource_dtor_call", 0, last_resource_dtor_call},
- {"make_new_resource", 2, make_new_resource}
+ {"make_new_resource", 2, make_new_resource},
+ {"check_is", 10, check_is},
+ {"length_test", 5, length_test},
+ {"make_atoms", 0, make_atoms},
+ {"make_strings", 0, make_strings}
};
diff --git a/erts/emulator/test/port_SUITE.erl b/erts/emulator/test/port_SUITE.erl
index eb69bf917b..66aff307a3 100644
--- a/erts/emulator/test/port_SUITE.erl
+++ b/erts/emulator/test/port_SUITE.erl
@@ -88,7 +88,7 @@
otp_3906/1, otp_4389/1, win_massive/1, win_massive_client/1,
mix_up_ports/1, otp_5112/1, otp_5119/1, otp_6224/1,
exit_status_multi_scheduling_block/1, ports/1,
- spawn_driver/1,spawn_executable/1,
+ spawn_driver/1, spawn_executable/1, close_deaf_port/1,
unregister_name/1]).
-export([]).
@@ -113,7 +113,7 @@ all(suite) ->
otp_3906, otp_4389, win_massive, mix_up_ports,
otp_5112, otp_5119,
exit_status_multi_scheduling_block,
- ports, spawn_driver, spawn_executable,
+ ports, spawn_driver, spawn_executable, close_deaf_port,
unregister_name
].
@@ -2293,3 +2293,12 @@ load_driver(Dir, Driver) ->
io:format("~s\n", [erl_ddll:format_error(Error)]),
Res
end.
+
+
+close_deaf_port(doc) -> ["Send data to port program that does not read it, then close port."];
+close_deaf_port(suite) -> [];
+close_deaf_port(Config) when is_list(Config) ->
+ Port = open_port({spawn,"sleep 999999"},[]),
+ erlang:port_command(Port,"Hello, can you hear me!?!?"),
+ port_close(Port),
+ ok.
diff --git a/lib/inets/doc/src/http_server.xml b/lib/inets/doc/src/http_server.xml
index 01e0b47d37..68dfd1add0 100644
--- a/lib/inets/doc/src/http_server.xml
+++ b/lib/inets/doc/src/http_server.xml
@@ -30,6 +30,8 @@
<date></date>
<rev></rev>
<file>http_server.xml</file>
+
+ <marker id="intro"></marker>
</header>
<section>
@@ -65,6 +67,8 @@
Server API. This API can be used to advantage by all who wants
to enhance the server core functionality, for example custom
logging and authentication.</p>
+
+ <marker id="config"></marker>
</section>
<section>
@@ -109,6 +113,8 @@
functions or only exported functions on chosen modules.</p>
<p>{accept_timeout, integer()} sets the wanted timeout value for
the server to set up a request connection.</p>
+
+ <marker id="using_http_server_api"></marker>
</section>
<section>
@@ -173,6 +179,7 @@
the ip address reported by the info function and can
not be the hostname that is allowed when inputting bind_address.</p>
+ <marker id="htaccess"></marker>
</section>
<section>
@@ -337,6 +344,8 @@ UserName:Password
</item>
</list>
</section>
+
+ <marker id="dynamic_we_pages"></marker>
</section>
<section>
@@ -434,6 +443,8 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[
</note>
</section>
</section>
+
+ <marker id="logging"></marker>
</section>
<section>
@@ -467,6 +478,8 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[
</p>
<p><em>[date]</em> access to <em>path</em> failed for
<em>remotehost</em>, reason: <em>reason</em></p>
+
+ <marker id="ssi"></marker>
</section>
<section>
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 3216b0c2cd..fed42291ab 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -32,6 +32,61 @@
<file>notes.xml</file>
</header>
+ <section><title>Inets 5.3.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <p>-</p>
+
+<!--
+ <list>
+ <item>
+ <p>[httpc] - Allow users to pass socket options to the transport
+ module when making requests. </p>
+ <p>See the <c>socket_opts</c> option in the
+ <seealso marker="httpc#request2">request/4</seealso> or
+ <seealso marker="httpc#set_options">set_options/1,2</seealso>
+ for more info, </p>
+ <p>Own Id: OTP-8352</p>
+ </item>
+
+ </list>
+-->
+ </section>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+
+<!--
+ <p>-</p>
+-->
+
+ <list>
+ <item>
+ <p>[httpc] - Made cookie handling more case insensitive.</p>
+ <p>Own Id: OTP-8609</p>
+ <p>Nicolas Thauvin</p>
+ </item>
+
+ <item>
+ <p>[httpc|httpd] - Netscape cookie dates can also be given with a
+ 2-digit year (e.g. 06 = 2006). </p>
+ <p>Own Id: OTP-8610</p>
+ <p>Nicolas Thauvin</p>
+ </item>
+
+ <item>
+ <p>[httpd] - Added support (again) for the documented debugging
+ features. See the User's Guide
+ <seealso marker="http_server#config">Configuration</seealso>
+ chapter for more info. </p>
+ <p>Own Id: OTP-8624</p>
+ </item>
+
+ </list>
+ </section>
+
+ </section> <!-- 5.3.3 -->
+
+
<section><title>Inets 5.3.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/inets/src/http_client/httpc_cookie.erl b/lib/inets/src/http_client/httpc_cookie.erl
index 586701b4a1..4d61f82b5a 100644
--- a/lib/inets/src/http_client/httpc_cookie.erl
+++ b/lib/inets/src/http_client/httpc_cookie.erl
@@ -476,13 +476,13 @@ path_sort(Cookies)->
lists:reverse(lists:keysort(#http_cookie.path, Cookies)).
-%% Informally, the Set-Cookie response header comprises the token
-%% Set-Cookie:, followed by a comma-separated list of one or more
-%% cookies. Netscape cookies expires attribute may also have a
-%% , in this case the header list will have been incorrectly split
-%% in parse_set_cookies/2 this functions fixs that problem.
+%% Informally, the Set-Cookie response header comprises the token
+%% Set-Cookie:, followed by a comma-separated list of one or more
+%% cookies. Netscape cookies expires attribute may also have a,
+%% in this case the header list will have been incorrectly split
+%% in parse_set_cookies/2 this functions fix that problem.
fix_netscape_cookie([Cookie1, Cookie2 | Rest], Acc) ->
- case inets_regexp:match(Cookie1, "expires=") of
+ case inets_regexp:match(string:to_lower(Cookie1), "expires=") of
{_, _, _} ->
fix_netscape_cookie(Rest, [Cookie1 ++ Cookie2 | Acc]);
nomatch ->
diff --git a/lib/inets/src/http_lib/http_util.erl b/lib/inets/src/http_lib/http_util.erl
index ddb58c7116..4f1147176c 100644
--- a/lib/inets/src/http_lib/http_util.erl
+++ b/lib/inets/src/http_lib/http_util.erl
@@ -38,13 +38,79 @@ to_upper(Str) ->
to_lower(Str) ->
string:to_lower(Str).
-convert_netscapecookie_date([_D,_A,_Y, $,, _SP,
- D1,D2,_DA,
- M,O,N,_DA,
- Y1,Y2,Y3,Y4,_SP,
- H1,H2,_Col,
- M1,M2,_Col,
+%% Example: Mon, 09-Dec-2002 13:46:00 GMT
+convert_netscapecookie_date([_D,_A,_Y, $,, $ ,
+ D1,D2, $-,
+ M,O,N, $-,
+ Y1,Y2,Y3,Y4, $ ,
+ H1,H2, $:,
+ M1,M2, $:,
+ S1,S2|_Rest]) ->
+ Year = list_to_integer([Y1,Y2,Y3,Y4]),
+ Day = list_to_integer([D1,D2]),
+ Month = convert_month([M,O,N]),
+ Hour = list_to_integer([H1,H2]),
+ Min = list_to_integer([M1,M2]),
+ Sec = list_to_integer([S1,S2]),
+ {{Year,Month,Day},{Hour,Min,Sec}};
+
+convert_netscapecookie_date([_D,_A,_Y, $,, $ ,
+ D1,D2, $-,
+ M,O,N, $-,
+ Y3,Y4, $ ,
+ H1,H2, $:,
+ M1,M2, $:,
+ S1,S2|_Rest]) ->
+ {CurrentYear, _, _} = date(),
+ [Y1,Y2|_] = integer_to_list(CurrentYear),
+ Year = list_to_integer([Y1,Y2,Y3,Y4]),
+ Day = list_to_integer([D1,D2]),
+ Month = convert_month([M,O,N]),
+ Hour = list_to_integer([H1,H2]),
+ Min = list_to_integer([M1,M2]),
+ Sec = list_to_integer([S1,S2]),
+ {{Year,Month,Day},{Hour,Min,Sec}};
+
+convert_netscapecookie_date([_D,_A,_Y, $ ,
+ D1,D2, $-,
+ M,O,N, $-,
+ Y1,Y2,Y3,Y4, $ ,
+ H1,H2, $:,
+ M1,M2, $:,
S1,S2|_Rest]) ->
+ Year = list_to_integer([Y1,Y2,Y3,Y4]),
+ Day = list_to_integer([D1,D2]),
+ Month = convert_month([M,O,N]),
+ Hour = list_to_integer([H1,H2]),
+ Min = list_to_integer([M1,M2]),
+ Sec = list_to_integer([S1,S2]),
+ {{Year,Month,Day},{Hour,Min,Sec}};
+
+convert_netscapecookie_date([_D,_A,_Y, $ ,
+ D1,D2, $-,
+ M,O,N, $-,
+ Y3,Y4, $ ,
+ H1,H2, $:,
+ M1,M2, $:,
+ S1,S2|_Rest]) ->
+ {CurrentYear, _, _} = date(),
+ [Y1,Y2|_] = integer_to_list(CurrentYear),
+ Year = list_to_integer([Y1,Y2,Y3,Y4]),
+ Day = list_to_integer([D1,D2]),
+ Month = convert_month([M,O,N]),
+ Hour = list_to_integer([H1,H2]),
+ Min = list_to_integer([M1,M2]),
+ Sec = list_to_integer([S1,S2]),
+ {{Year,Month,Day},{Hour,Min,Sec}};
+
+%% Sloppy...
+convert_netscapecookie_date([_D,_A,_Y, $,, _SP,
+ D1,D2,_DA,
+ M,O,N,_DA,
+ Y1,Y2,Y3,Y4,_SP,
+ H1,H2,_Col,
+ M1,M2,_Col,
+ S1,S2|_Rest]) ->
Year=list_to_integer([Y1,Y2,Y3,Y4]),
Day=list_to_integer([D1,D2]),
Month=convert_month([M,O,N]),
@@ -54,12 +120,12 @@ convert_netscapecookie_date([_D,_A,_Y, $,, _SP,
{{Year,Month,Day},{Hour,Min,Sec}};
convert_netscapecookie_date([_D,_A,_Y, _SP,
- D1,D2,_DA,
- M,O,N,_DA,
- Y1,Y2,Y3,Y4,_SP,
- H1,H2,_Col,
- M1,M2,_Col,
- S1,S2|_Rest]) ->
+ D1,D2,_DA,
+ M,O,N,_DA,
+ Y1,Y2,Y3,Y4,_SP,
+ H1,H2,_Col,
+ M1,M2,_Col,
+ S1,S2|_Rest]) ->
Year=list_to_integer([Y1,Y2,Y3,Y4]),
Day=list_to_integer([D1,D2]),
Month=convert_month([M,O,N]),
@@ -68,17 +134,17 @@ convert_netscapecookie_date([_D,_A,_Y, _SP,
Sec=list_to_integer([S1,S2]),
{{Year,Month,Day},{Hour,Min,Sec}}.
-hexlist_to_integer([])->
+hexlist_to_integer([]) ->
empty;
%%When the string only contains one value its eaasy done.
%% 0-9
-hexlist_to_integer([Size]) when Size >= 48 , Size =< 57 ->
+hexlist_to_integer([Size]) when (Size >= 48) andalso (Size =< 57) ->
Size - 48;
%% A-F
-hexlist_to_integer([Size]) when Size >= 65 , Size =< 70 ->
+hexlist_to_integer([Size]) when (Size >= 65) andalso (Size =< 70) ->
Size - 55;
%% a-f
-hexlist_to_integer([Size]) when Size >= 97 , Size =< 102 ->
+hexlist_to_integer([Size]) when (Size >= 97) andalso (Size =< 102) ->
Size - 87;
hexlist_to_integer([_Size]) ->
not_a_num;
@@ -141,7 +207,7 @@ hexlist_to_integer2([HexVal | HexString], Pos, Sum)
hexlist_to_integer2(_AfterHexString, _Pos, Sum)->
Sum.
-integer_to_hexlist(Num, Pot, Res) when Pot<0 ->
+integer_to_hexlist(Num, Pot, Res) when Pot < 0 ->
convert_to_ascii([Num | Res]);
integer_to_hexlist(Num,Pot,Res) ->
@@ -163,7 +229,9 @@ convert_to_ascii(RevesedNum) ->
convert_to_ascii([], Num)->
Num;
-convert_to_ascii([Num | Reversed], Number) when Num > -1, Num < 10 ->
+convert_to_ascii([Num | Reversed], Number)
+ when (Num > -1) andalso (Num < 10) ->
convert_to_ascii(Reversed, [Num + 48 | Number]);
-convert_to_ascii([Num | Reversed], Number) when Num > 9, Num < 16 ->
+convert_to_ascii([Num | Reversed], Number)
+ when (Num > 9) andalso (Num < 16) ->
convert_to_ascii(Reversed, [Num + 55 | Number]).
diff --git a/lib/inets/src/http_server/httpd_instance_sup.erl b/lib/inets/src/http_server/httpd_instance_sup.erl
index 0aaeb838c2..baa60d318c 100644
--- a/lib/inets/src/http_server/httpd_instance_sup.erl
+++ b/lib/inets/src/http_server/httpd_instance_sup.erl
@@ -97,14 +97,16 @@ start_link(ConfigFile, AcceptTimeout, ListenInfo, Debug) ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-init([ConfigFile, ConfigList, AcceptTimeout, _Debug, Address, Port]) ->
+init([ConfigFile, ConfigList, AcceptTimeout, Debug, Address, Port]) ->
+ httpd_util:enable_debug(Debug),
Flags = {one_for_one, 0, 1},
Children = [sup_spec(httpd_acceptor_sup, Address, Port),
sup_spec(httpd_misc_sup, Address, Port),
worker_spec(httpd_manager, Address, Port,
ConfigFile, ConfigList,AcceptTimeout)],
{ok, {Flags, Children}};
-init([ConfigFile, ConfigList, AcceptTimeout, _Debug, Address, Port, ListenInfo]) ->
+init([ConfigFile, ConfigList, AcceptTimeout, Debug, Address, Port, ListenInfo]) ->
+ httpd_util:enable_debug(Debug),
Flags = {one_for_one, 0, 1},
Children = [sup_spec(httpd_acceptor_sup, Address, Port),
sup_spec(httpd_misc_sup, Address, Port),
diff --git a/lib/inets/src/http_server/httpd_sup.erl b/lib/inets/src/http_server/httpd_sup.erl
index 3399f78b53..1507c6852a 100644
--- a/lib/inets/src/http_server/httpd_sup.erl
+++ b/lib/inets/src/http_server/httpd_sup.erl
@@ -185,14 +185,14 @@ httpd_child_spec(ConfigFile, AcceptTimeout, Debug) ->
httpd_child_spec(Config, AcceptTimeout, Debug, Addr, 0) ->
case start_listen(Addr, 0, Config) of
{Pid, {NewPort, NewConfig, ListenSocket}} ->
- Name = {httpd_instance_sup, Addr, NewPort},
+ Name = {httpd_instance_sup, Addr, NewPort},
StartFunc = {httpd_instance_sup, start_link,
[NewConfig, AcceptTimeout,
{Pid, ListenSocket}, Debug]},
- Restart = permanent,
- Shutdown = infinity,
- Modules = [httpd_instance_sup],
- Type = supervisor,
+ Restart = permanent,
+ Shutdown = infinity,
+ Modules = [httpd_instance_sup],
+ Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules};
{Pid, {error, Reason}} ->
exit(Pid, normal),
diff --git a/lib/inets/src/http_server/httpd_util.erl b/lib/inets/src/http_server/httpd_util.erl
index b59fd861dc..cfad79638f 100644
--- a/lib/inets/src/http_server/httpd_util.erl
+++ b/lib/inets/src/http_server/httpd_util.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -755,23 +755,18 @@ do_enable_debug([{Level,Modules}|Rest])
when is_atom(Level) andalso is_list(Modules) ->
case Level of
all_functions ->
- io:format("Tracing on all functions set on modules: ~p~n",
- [Modules]),
lists:foreach(
- fun(X)->
+ fun(X) ->
dbg:tpl(X, [{'_', [], [{return_trace}]}])
end, Modules);
exported_functions ->
- io:format("Tracing on exported functions set on "
- "modules: ~p~n",[Modules]),
lists:foreach(
- fun(X)->
+ fun(X) ->
dbg:tp(X, [{'_', [], [{return_trace}]}])
end, Modules);
disable ->
- io:format("Tracing disabled on modules: ~p~n", [Modules]),
lists:foreach(
- fun(X)->
+ fun(X) ->
dbg:ctp(X)
end, Modules);
_ ->
diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src
index dfdfb41373..718f37b09e 100644
--- a/lib/inets/src/inets_app/inets.appup.src
+++ b/lib/inets/src/inets_app/inets.appup.src
@@ -18,16 +18,26 @@
{"%VSN%",
[
+ {"5.3.2",
+ [
+ {load_module, http_util, soft_purge, soft_purge, []},
+ {load_module, httpc_cookie, soft_purge, soft_purge, []}
+ ]
+ },
{"5.3.1",
[
+ {load_module, http_util, soft_purge, soft_purge, []},
{load_module, httpc, soft_purge, soft_purge, []},
+ {load_module, httpc_cookie, soft_purge, soft_purge, []},
{update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]},
{update, httpc_manager, soft, soft_purge, soft_purge, []}
]
},
{"5.3",
[
+ {load_module, http_util, soft_purge, soft_purge, []},
{load_module, httpc, soft_purge, soft_purge, []},
+ {load_module, httpc_cookie, soft_purge, soft_purge, []},
{update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]},
{update, httpc_manager, soft, soft_purge, soft_purge, []},
{load_module, mod_esi, soft_purge, soft_purge, []}
@@ -50,16 +60,26 @@
}
],
[
+ {"5.3.2",
+ [
+ {load_module, http_util, soft_purge, soft_purge, []},
+ {load_module, httpc_cookie, soft_purge, soft_purge, []}
+ ]
+ },
{"5.3.1",
[
+ {load_module, http_util, soft_purge, soft_purge, []},
{load_module, httpc, soft_purge, soft_purge, []},
+ {load_module, httpc_cookie, soft_purge, soft_purge, []},
{update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]},
{update, httpc_manager, soft, soft_purge, soft_purge, []}
]
},
{"5.3",
[
+ {load_module, http_util, soft_purge, soft_purge, []},
{load_module, httpc, soft_purge, soft_purge, []},
+ {load_module, httpc_cookie, soft_purge, soft_purge, []},
{update, httpc_handler, soft, soft_purge, soft_purge, [httpc_manager]},
{update, httpc_manager, soft, soft_purge, soft_purge, []},
{load_module, mod_esi, soft_purge, soft_purge, []}
diff --git a/lib/inets/src/inets_app/inets.erl b/lib/inets/src/inets_app/inets.erl
index 7e3f862ee7..f1fa5fd997 100644
--- a/lib/inets/src/inets_app/inets.erl
+++ b/lib/inets/src/inets_app/inets.erl
@@ -533,7 +533,7 @@ error_to_exit(Where, {error, Reason}) ->
%%-----------------------------------------------------------------
-%% report_event(Serverity, Label, Service, Content)
+%% report_event(Severity, Label, Service, Content)
%%
%% Parameters:
%% Severity -> 0 =< integer() =< 100
diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl
index 9559317640..79945f0f4d 100644
--- a/lib/inets/test/http_format_SUITE.erl
+++ b/lib/inets/test/http_format_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -567,6 +567,12 @@ convert_netscapecookie_date(Config) when is_list(Config) ->
http_util:convert_netscapecookie_date("Sun, 12-Nov-2006 08:59:38 GMT"),
{{2006,12,12},{8,59,38}} =
http_util:convert_netscapecookie_date("Sun, 12-Dec-2006 08:59:38 GMT"),
+ {{2006,12,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun 12-Dec-2006 08:59:38 GMT"),
+ {{2006,12,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun, 12-Dec-06 08:59:38 GMT"),
+ {{2006,12,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun 12-Dec-06 08:59:38 GMT"),
ok.
%%--------------------------------------------------------------------
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index 7776bef0a5..ac20fa7bb7 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -18,11 +18,15 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 5.3.2
+INETS_VSN = 5.3.3
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
-TICKETS = OTP-8542 OTP-8607
+TICKETS = OTP-8609 OTP-8610 OTP-8624
+
+TICKETS_5_3_2 = \
+ OTP-8542 \
+ OTP-8607
TICKETS_5_3_1 = \
OTP-8508 \
@@ -42,7 +46,14 @@ TICKETS_5_3 = \
OTP-8359 \
OTP-8371
-TICKETS_5_2 = OTP-8204 OTP-8206 OTP-8247 OTP-8248 OTP-8249 OTP-8258 OTP-8280
+TICKETS_5_2 = \
+ OTP-8204 \
+ OTP-8206 \
+ OTP-8247 \
+ OTP-8248 \
+ OTP-8249 \
+ OTP-8258 \
+ OTP-8280
TICKETS_5_1_3 = OTP-8154
diff --git a/lib/odbc/test/Makefile b/lib/odbc/test/Makefile
new file mode 100644
index 0000000000..935ecbf5a7
--- /dev/null
+++ b/lib/odbc/test/Makefile
@@ -0,0 +1,114 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1999-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+
+INCLUDES= -I. -I$(ERL_TOP)/lib/test_server/include/ -I$(ERL_TOP)/lib/odbc/src
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+MODULES= \
+ odbc_start_SUITE \
+ odbc_connect_SUITE \
+ odbc_query_SUITE \
+ odbc_data_type_SUITE \
+ odbc_test_lib \
+ oracle \
+ sqlserver \
+ postgres
+
+EBIN = .
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+HRL_FILES= odbc_test.hrl\
+
+TARGET_FILES= \
+ $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+SPEC_FILES = odbc.spec odbc.dynspec \
+ odbc.spec.win
+
+EMAKEFILE = Emakefile
+MAKE_EMAKE = $(wildcard $(ERL_TOP)/make/make_emakefile)
+
+ifeq ($(MAKE_EMAKE),)
+BUILDTARGET = $(TARGET_FILES)
+RELTEST_FILES = $(SPEC_FILES) $(SOURCE)
+else
+BUILDTARGET = emakebuild
+RELTEST_FILES = $(EMAKEFILE) $(SPEC_FILES) $(SOURCE)
+endif
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/odbc_test
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+
+ERL_COMPILE_FLAGS += $(INCLUDES) \
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+tests debug opt: $(BUILDTARGET)
+
+targets: $(TARGET_FILES)
+
+.PHONY: emakebuild
+
+emakebuild: $(EMAKEFILE)
+
+$(EMAKEFILE):
+ $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make' | grep -v Warning > $(EMAKEFILE)
+ $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) | grep -v Warning >> $(EMAKEFILE)
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f core
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+
+release_tests_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) $(SPEC_FILES) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)
+
+
+release_docs_spec:
+
+
+
+
+
+
+
diff --git a/lib/odbc/test/README b/lib/odbc/test/README
new file mode 100644
index 0000000000..1f3c659e28
--- /dev/null
+++ b/lib/odbc/test/README
@@ -0,0 +1,86 @@
+-------------------------------------------------------------------------
+ TEST SUITE REQUIREMENTS
+-------------------------------------------------------------------------
+As third party products are involved when using ODBC you will have to
+setup your own test environment to be able to run the ODBC test
+suites.
+
+You need to install a database such as postgres, sql-server, oracle
+etc, and ODBC-drivers for that database.
+
+Then you need to setup a test database, however you do not
+need to create any tables that will be done by the test suites.
+The test suites will also remove all tables that it creates when
+the test is complete.
+
+-------------------------------------------------------------------------
+ERLANG FILES YOU MAY NEED TO CHANGE
+-------------------------------------------------------------------------
+
+A remote database management system has a callback module to handle
+possible differences in data type handling etc, the callback module
+also defines the ODBC connection string. Currently available callback
+modules are postgres.erl, sqlserver.erl and oracle.erl. Depending on
+how you set things up you might want to edit the connection string in
+the callback module or even add your own callback module.
+
+The callback module used in each test case is defined by the ?RDBMS
+macro defined in odbc_test.hrl so you might need to change this to
+suite your purposes.
+
+-------------------------------------------------------------------------
+EXAMPLE
+-------------------------------------------------------------------------
+
+As an example say we have the database odbctestdb, with
+the user odbctest that has the password Sesame. The database
+runs on the host myhost.
+
+UINX/LINUX
+-----------
+
+Set up a database and install the unixODBC drivers.
+Then the unix/linux user that should run the test suits needs an .odbc.ini
+file to map connection data. For example ODBC connection string:
+"DSN=Postgres;UID=odbctest" will need an .odbc.ini entry that looks
+something like this:
+
+--- Start example of .odbc.ini ----
+
+[Postgres]
+Driver=/usr/lib/psqlodbc.so
+Description=Postgres driver
+ServerName=myhost
+Database=odbctestdb
+Port=5432
+LogonID=odbctest
+Password=Sesame
+
+---End example of .odbc.ini ------------
+
+
+WINDOWS MOST FLAVORS
+--------------------
+
+There will be a "ODBC data source administrator" tool under
+Control Panel -> Administrative Tools, use this to set up
+your database. Choose to connect with SQL Server authentication.
+As odbc connection string use: "DSN=odbctestdb;UID=odbctest;PWD=Sesame"
+
+
+> %CopyrightBegin%
+>
+> Copyright Ericsson AB 2010. All Rights Reserved.
+>
+> The contents of this file are subject to the Erlang Public License,
+> Version 1.1, (the "License"); you may not use this file except in
+> compliance with the License. You should have received a copy of the
+> Erlang Public License along with this software. If not, it can be
+> retrieved online at http://www.erlang.org/.
+>
+> Software distributed under the License is distributed on an "AS IS"
+> basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+> the License for the specific language governing rights and limitations
+> under the License.
+>
+> %CopyrightEnd%
diff --git a/lib/odbc/test/odbc.dynspec b/lib/odbc/test/odbc.dynspec
new file mode 100644
index 0000000000..bb15edceed
--- /dev/null
+++ b/lib/odbc/test/odbc.dynspec
@@ -0,0 +1,31 @@
+%% -*- erlang -*-
+%% You can test this file using this command.
+%% file:script("odbc.dynspec", [{'Os',"Unix"}]).
+
+Exists =
+fun() ->
+ case code:lib_dir(odbc) of
+ {error,bad_name} ->
+ false;
+ P ->
+ %% Make sure that the odbc directory really
+ %% contains the application (and not only documentation).
+ case filelib:is_file(filename:join(P, "ebin/odbc.beam")) of
+ false -> false;
+ true ->
+ %% We know that we don't have any odbc libraries
+ %% installed on this computer.
+ {ok,Host} = inet:gethostname(),
+ Host =/= "netsim200"
+ end
+ end
+end,
+case Exists() of
+ false ->
+ NoOdbc = "No odbc application",
+ [{skip, {odbc_connect_SUITE, NoOdbc}},
+ {skip, {odbc_data_type_SUITE, NoOdbc}},
+ {skip, {odbc_query_SUITE, NoOdbc}}];
+ true ->
+ []
+end.
diff --git a/lib/odbc/test/odbc.spec b/lib/odbc/test/odbc.spec
new file mode 100644
index 0000000000..acba9f8d98
--- /dev/null
+++ b/lib/odbc/test/odbc.spec
@@ -0,0 +1,9 @@
+{topcase, {dir, "../odbc_test"}}.
+{skip, {odbc_data_type_SUITE, varchar_upper_limit, "Known bug in database"}}.
+{skip, {odbc_data_type_SUITE, text_upper_limit, "Consumes too much resources"}}.
+{skip, {odbc_data_type_SUITE, bit_true , "Not supported by driver"}}.
+{skip, {odbc_data_type_SUITE, bit_false, "Not supported by driver"}}.
+{skip, {odbc_query_SUITE, multiple_select_result_sets,"Not supported by driver"}}.
+{skip, {odbc_query_SUITE, multiple_mix_result_sets, "Not supported by driver"}}.
+{skip, {odbc_query_SUITE, multiple_result_sets_error, "Not supported by driver"}}.
+{skip, {odbc_query_SUITE, param_insert_tiny_int, "Not supported by driver"}}. \ No newline at end of file
diff --git a/lib/odbc/test/odbc.spec.win b/lib/odbc/test/odbc.spec.win
new file mode 100644
index 0000000000..1fd349d2c3
--- /dev/null
+++ b/lib/odbc/test/odbc.spec.win
@@ -0,0 +1,5 @@
+{topcase, {dir, "../odbc_test"}}.
+{skip, {odbc_data_type_SUITE, big_int_lower_limit, "Not supported by sqlserver 7.0"}}.
+{skip, {odbc_data_type_SUITE, big_int_upper_limit, "Not supported by sqlserver7.0"}}.
+{skip, {odbc_data_type_SUITE, text_upper_limit, "Consumes too much resources"}}.
+
diff --git a/lib/odbc/test/odbc_connect_SUITE.erl b/lib/odbc/test/odbc_connect_SUITE.erl
new file mode 100644
index 0000000000..4d37a8f543
--- /dev/null
+++ b/lib/odbc/test/odbc_connect_SUITE.erl
@@ -0,0 +1,816 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(odbc_connect_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("odbc_test.hrl").
+
+-define(MAX_SEQ_TIMEOUTS, 10).
+
+%%--------------------------------------------------------------------
+%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
+%% Arg - doc | suite
+%% Doc - string()
+%% Case - atom()
+%% Name of a test case function.
+%% Comment - string()
+%% Description: Returns documentation/test cases in this test suite
+%% or a skip tuple if the platform is not supported.
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Tests the ability to connect and disconnet to/from the database"];
+all(suite) ->
+ case odbc_test_lib:odbc_check() of
+ ok -> all();
+ Other -> {skip, Other}
+ end.
+
+all() ->
+ [not_exist_db, commit, rollback, not_explicit_commit,
+ no_c_node, port_dies, control_process_dies, client_dies,
+ connect_timeout, timeout, many_timeouts, timeout_reset,
+ disconnect_on_timeout, connection_closed,
+ disable_scrollable_cursors, return_rows_as_lists, api_missuse].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ application:start(odbc),
+ case odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]) of
+ {ok, Ref} ->
+ odbc:disconnect(Ref),
+ [{tableName, odbc_test_lib:unique_table_name()} | Config];
+ _ ->
+ {skip, "ODBC is not properly setup"}
+ end.
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ application:stop(odbc),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]),
+ Dog = test_server:timetrap(?default_timeout),
+ Temp = lists:keydelete(connection_ref, 1, Config),
+ NewConfig = lists:keydelete(watchdog, 1, Temp),
+ [{watchdog, Dog} | NewConfig].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, Config) ->
+ %% Clean up if needed
+ Table = ?config(tableName, Config),
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Result = odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+ io:format("Drop table: ~p ~p~n", [Table, Result]),
+ odbc:disconnect(Ref),
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+commit(doc)->
+ ["Test the use of explicit commit"];
+commit(suite) -> [];
+commit(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+
+ Table = ?config(tableName, Config),
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1,'bar')"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ ok = odbc:commit(Ref, commit),
+ UpdateResult = ?RDBMS:update_result(),
+ UpdateResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'bar' WHERE ID = 1"),
+ ok = odbc:commit(Ref, commit, ?TIMEOUT),
+ InsertResult = ?RDBMS:insert_result(),
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT', {function_clause, _}} =
+ (catch odbc:commit(Ref, commit, -1)),
+
+ ok = odbc:disconnect(Ref),
+
+ ok.
+%%-------------------------------------------------------------------------
+
+rollback(doc)->
+ ["Test the use of explicit rollback"];
+rollback(suite) -> [];
+rollback(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+ ok = odbc:commit(Ref, rollback),
+ InsertResult = ?RDBMS:insert_result(),
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+ ok = odbc:commit(Ref, rollback, ?TIMEOUT),
+ InsertResult = ?RDBMS:insert_result(),
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+
+ {'EXIT', {function_clause, _}} =
+ (catch odbc:commit(Ref, rollback, -1)),
+
+ ok = odbc:disconnect(Ref),
+ ok.
+
+%%-------------------------------------------------------------------------
+not_explicit_commit(doc) ->
+ ["Test what happens if you try using commit on a auto_commit connection."];
+not_explicit_commit(suite) -> [];
+not_explicit_commit(_Config) ->
+ {ok, Ref} =
+ odbc:connect(?RDBMS:connection_string(), [{auto_commit, on}]),
+ {error, _} = odbc:commit(Ref, commit),
+ ok = odbc:disconnect(Ref),
+ ok.
+
+%%-------------------------------------------------------------------------
+not_exist_db(doc) ->
+ ["Tests valid data format but invalid data in the connection parameters."];
+not_exist_db(suite) -> [];
+not_exist_db(_Config) ->
+ {error, _} = odbc:connect("DSN=foo;UID=bar;PWD=foobar", []),
+ %% So that the odbc control server can be stoped "in the correct way"
+ test_server:sleep(100),
+ ok.
+
+%%-------------------------------------------------------------------------
+no_c_node(doc) ->
+ "Test what happens if the port-program can not be found";
+no_c_node(suite) -> [];
+no_c_node(_Config) ->
+ process_flag(trap_exit, true),
+ Dir = filename:nativename(filename:join(code:priv_dir(odbc),
+ "bin")),
+ FileName1 = filename:nativename(os:find_executable("odbcserver",
+ Dir)),
+ FileName2 = filename:nativename(filename:join(Dir, "odbcsrv")),
+ ok = file:rename(FileName1, FileName2),
+ Result =
+ case catch odbc:connect(?RDBMS:connection_string(), []) of
+ {error, port_program_executable_not_found} ->
+ ok;
+ Else ->
+ Else
+ end,
+
+ ok = file:rename(FileName2, FileName1),
+ ok = Result.
+%%------------------------------------------------------------------------
+
+port_dies(doc) ->
+ "Tests what happens if the port program dies";
+port_dies(suite) -> [];
+port_dies(_Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ {status, _} = process_info(Ref, status),
+ process_flag(trap_exit, true),
+ Port = lists:last(erlang:ports()),
+ exit(Port, kill),
+ %% Wait for exit_status from port 5000 ms (will not get a exit
+ %% status in this case), then wait a little longer to make sure
+ %% the port and the controlprocess has had time to terminate.
+ test_server:sleep(7000),
+ undefined = process_info(Ref, status),
+ ok.
+
+%%-------------------------------------------------------------------------
+control_process_dies(doc) ->
+ "Tests what happens if the Erlang control process dies";
+control_process_dies(suite) -> [];
+control_process_dies(_Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ process_flag(trap_exit, true),
+ Port = lists:last(erlang:ports()),
+ {connected, Ref} = erlang:port_info(Port, connected),
+ exit(Ref, kill),
+ test_server:sleep(100),
+ undefined = erlang:port_info(Port, connected),
+ %% Check for c-program still running, how?
+ ok.
+
+%%-------------------------------------------------------------------------
+client_dies(doc) ->
+ ["Test that the odbc process is terminated when the client process "
+ "dies"];
+client_dies(suite) ->
+ [client_dies_normal, client_dies_timeout, client_dies_error].
+
+%%-------------------------------------------------------------------------
+client_dies_normal(doc) ->
+ ["Client dies with reason normal."];
+client_dies_normal(suite) -> [];
+client_dies_normal(Config) when is_list(Config) ->
+ Pid = spawn(?MODULE, client_normal, [self()]),
+
+ MonitorReference =
+ receive
+ {dbRef, Ref} ->
+ MRef = erlang:monitor(process, Ref),
+ Pid ! continue,
+ MRef
+ end,
+
+ receive
+ {'DOWN', MonitorReference, _Type, _Object, _Info} ->
+ ok
+ after 5000 ->
+ test_server:fail(control_process_not_stopped)
+ end.
+
+client_normal(Pid) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Pid ! {dbRef, Ref},
+ receive
+ continue ->
+ ok
+ end,
+ exit(self(), normal).
+
+
+%%-------------------------------------------------------------------------
+client_dies_timeout(doc) ->
+ ["Client dies with reason timeout."];
+client_dies_timeout(suite) -> [];
+client_dies_timeout(Config) when is_list(Config) ->
+ Pid = spawn(?MODULE, client_timeout, [self()]),
+
+ MonitorReference =
+ receive
+ {dbRef, Ref} ->
+ MRef = erlang:monitor(process, Ref),
+ Pid ! continue,
+ MRef
+ end,
+
+ receive
+ {'DOWN', MonitorReference, _Type, _Object, _Info} ->
+ ok
+ after 5000 ->
+ test_server:fail(control_process_not_stopped)
+ end.
+
+client_timeout(Pid) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Pid ! {dbRef, Ref},
+ receive
+ continue ->
+ ok
+ end,
+ exit(self(), timeout).
+
+
+%%-------------------------------------------------------------------------
+client_dies_error(doc) ->
+ ["Client dies with reason error."];
+client_dies_error(suite) -> [];
+client_dies_error(Config) when is_list(Config) ->
+ Pid = spawn(?MODULE, client_error, [self()]),
+
+ MonitorReference =
+ receive
+ {dbRef, Ref} ->
+ MRef = erlang:monitor(process, Ref),
+ Pid ! continue,
+ MRef
+ end,
+
+ receive
+ {'DOWN', MonitorReference, _Type, _Object, _Info} ->
+ ok
+ after 5000 ->
+ test_server:fail(control_process_not_stopped)
+ end.
+
+client_error(Pid) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Pid ! {dbRef, Ref},
+ receive
+ continue ->
+ ok
+ end,
+ exit(self(), error).
+
+
+%%-------------------------------------------------------------------------
+connect_timeout(doc) ->
+ ["Test the timeout for the connect function."];
+connect_timeout(suite) -> [];
+connect_timeout(Config) when is_list(Config) ->
+ {'EXIT',timeout} = (catch odbc:connect(?RDBMS:connection_string(),
+ [{timeout, 0}])),
+ ok.
+%%-------------------------------------------------------------------------
+timeout(doc) ->
+ ["Test that timeouts don't cause unwanted behavior sush as receiving"
+ " an anwser to a previously tiemed out query."];
+timeout(suite) -> [];
+timeout(Config) when is_list(Config) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'baz')"),
+
+ Pid = spawn_link(?MODULE, update_table_timeout, [Table, 5000, self()]),
+
+ receive
+ timout_occurred ->
+ ok = odbc:commit(Ref, commit),
+ Pid ! continue
+ end,
+
+ receive
+ altered ->
+ ok
+ end,
+
+ {selected, Fields, [{"foobar"}]} =
+ odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 1"),
+ ["DATA"] = odbc_test_lib:to_upper(Fields),
+
+ ok = odbc:commit(Ref, commit),
+ ok = odbc:disconnect(Ref),
+ ok.
+
+
+update_table_timeout(Table, TimeOut, Pid) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
+
+ case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
+ {'EXIT', timeout} ->
+ Pid ! timout_occurred;
+ {updated, 1} ->
+ test_server:fail(database_locker_failed)
+ end,
+
+ receive
+ continue ->
+ ok
+ end,
+
+ %% Make sure we receive the correct result and not the answer
+ %% to the previous query.
+ {selected, Fields, [{"baz"}]} =
+ odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"),
+ ["DATA"] = odbc_test_lib:to_upper(Fields),
+
+ {updated, 1} = odbc:sql_query(Ref, UpdateQuery, TimeOut),
+
+ ok = odbc:commit(Ref, commit),
+
+ Pid ! altered,
+
+ ok = odbc:disconnect(Ref),
+
+ ok.
+%%-------------------------------------------------------------------------
+many_timeouts(doc) ->
+ ["Tests that many consecutive timeouts lead to that the connection "
+ "is shutdown."];
+many_timeouts(suite) -> [];
+many_timeouts(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ _Pid = spawn_link(?MODULE, update_table_many_timeouts,
+ [Table, 5000, self()]),
+
+ receive
+ many_timeouts_occurred ->
+ ok
+ end,
+
+ ok = odbc:commit(Ref, commit),
+ ok = odbc:disconnect(Ref),
+ ok.
+
+
+update_table_many_timeouts(Table, TimeOut, Pid) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
+
+ ok = loop_many_timouts(Ref, UpdateQuery, TimeOut),
+
+ Pid ! many_timeouts_occurred,
+
+ ok = odbc:disconnect(Ref),
+ ok.
+
+
+loop_many_timouts(Ref, UpdateQuery, TimeOut) ->
+ case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
+ {'EXIT',timeout} ->
+ loop_many_timouts(Ref, UpdateQuery, TimeOut);
+ {updated, 1} ->
+ test_server:fail(database_locker_failed);
+ {error, connection_closed} ->
+ ok
+ end.
+%%-------------------------------------------------------------------------
+timeout_reset(doc) ->
+ ["Check that the number of consecutive timouts is reset to 0 when "
+ "a successful call to the database is made."];
+timeout_reset(suite) -> [];
+timeout_reset(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'baz')"),
+
+
+ Pid = spawn_link(?MODULE, update_table_timeout_reset,
+ [Table, 5000, self()]),
+
+ receive
+ many_timeouts_occurred ->
+ ok
+ end,
+
+ ok = odbc:commit(Ref, commit),
+ Pid ! continue,
+
+ receive
+ altered ->
+ ok
+ end,
+
+ {selected, Fields, [{"foobar"}]} =
+ odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 1"),
+ ["DATA"] = odbc_test_lib:to_upper(Fields),
+
+ ok = odbc:commit(Ref, commit),
+ ok = odbc:disconnect(Ref),
+ ok.
+
+update_table_timeout_reset(Table, TimeOut, Pid) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
+
+ ok = loop_timout_reset(Ref, UpdateQuery, TimeOut,
+ ?MAX_SEQ_TIMEOUTS-1),
+
+ Pid ! many_timeouts_occurred,
+
+ receive
+ continue ->
+ ok
+ end,
+
+ {selected, Fields, [{"baz"}]} =
+ odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"),
+ ["DATA"] = odbc_test_lib:to_upper(Fields),
+
+ {updated,1} = odbc:sql_query(Ref, UpdateQuery, TimeOut),
+
+ ok = odbc:commit(Ref, commit),
+
+ Pid ! altered,
+
+ ok = odbc:disconnect(Ref),
+
+ ok.
+
+loop_timout_reset(_, _, _, 0) ->
+ ok;
+
+loop_timout_reset(Ref, UpdateQuery, TimeOut, NumTimeouts) ->
+ case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
+ {'EXIT',timeout} ->
+ loop_timout_reset(Ref, UpdateQuery,
+ TimeOut, NumTimeouts - 1);
+ {updated, 1} ->
+ test_server:fail(database_locker_failed);
+ {error, connection_closed} ->
+ test_server:fail(connection_closed_premature)
+ end.
+
+%%-------------------------------------------------------------------------
+
+disconnect_on_timeout(doc) ->
+ ["Check that disconnect after a time out works properly"];
+disconnect_on_timeout(suite) -> [];
+disconnect_on_timeout(Config) when is_list(Config) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+
+ _Pid = spawn_link(?MODULE, update_table_disconnect_on_timeout,
+ [Table, 5000, self()]),
+ receive
+ ok ->
+ ok = odbc:commit(Ref, commit);
+ nok ->
+ test_server:fail(database_locker_failed)
+ end.
+
+update_table_disconnect_on_timeout(Table, TimeOut, Pid) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
+
+ case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
+ {'EXIT', timeout} ->
+ ok = odbc:disconnect(Ref),
+ Pid ! ok;
+ {updated, 1} ->
+ Pid ! nok
+ end.
+
+%%-------------------------------------------------------------------------
+connection_closed(doc) ->
+ ["Checks that you get an appropriate error message if you try to"
+ " use a connection that has been closed"];
+connection_closed(suite) -> [];
+connection_closed(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+
+ Table = ?config(tableName, Config),
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA char(10), PRIMARY KEY(ID))"),
+
+ ok = odbc:disconnect(Ref),
+
+ {error, connection_closed} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+ {error, connection_closed} =
+ odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+ {error, connection_closed} = odbc:first(Ref),
+ {error, connection_closed} = odbc:last(Ref),
+ {error, connection_closed} = odbc:next(Ref),
+ {error, connection_closed} = odbc:prev(Ref),
+ {error, connection_closed} = odbc:select(Ref, next, 3),
+ {error, connection_closed} = odbc:commit(Ref, commit),
+ ok.
+
+%%-------------------------------------------------------------------------
+disable_scrollable_cursors(doc) ->
+ ["Test disabling of scrollable cursors."];
+disable_scrollable_cursors(suite) -> [];
+disable_scrollable_cursors(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{scrollable_cursors, off}]),
+
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT ID FROM " ++ Table),
+
+ NextResult = ?RDBMS:selected_ID(1, next),
+
+ test_server:format("Expected: ~p~n", [NextResult]),
+
+ Result = odbc:next(Ref),
+ test_server:format("Got: ~p~n", [Result]),
+ NextResult = Result,
+
+ {error, scrollable_cursors_disabled} = odbc:first(Ref),
+ {error, scrollable_cursors_disabled} = odbc:last(Ref),
+ {error, scrollable_cursors_disabled} = odbc:prev(Ref),
+ {error, scrollable_cursors_disabled} =
+ odbc:select(Ref, {relative, 2}, 5),
+ {error, scrollable_cursors_disabled} =
+ odbc:select(Ref, {absolute, 2}, 5),
+
+ {selected, _ColNames,[]} = odbc:select(Ref, next, 1),
+ ok.
+
+%%-------------------------------------------------------------------------
+return_rows_as_lists(doc)->
+ ["Test the option that a row may be returned as a list instead "
+ "of a tuple. Too be somewhat backward compatible."];
+return_rows_as_lists(suite) -> [];
+return_rows_as_lists(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{tuple_row, off}]),
+
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'foo')"),
+
+ ListRows = ?RDBMS:selected_list_rows(),
+ ListRows =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ First = ?RDBMS:first_list_rows(),
+ Last = ?RDBMS:last_list_rows(),
+ Prev = ?RDBMS:prev_list_rows(),
+ Next = ?RDBMS:next_list_rows(),
+
+ Last = odbc:last(Ref),
+ Prev = odbc:prev(Ref),
+ First = odbc:first(Ref),
+ Next = odbc:next(Ref),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+api_missuse(doc)->
+ ["Test that behaviour of the control process if the api is abused"];
+api_missuse(suite) -> [];
+api_missuse(Config) when is_list(Config)->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ %% Serious programming fault, connetion will be shut down
+ gen_server:call(Ref, {self(), foobar, 10}, infinity),
+ test_server:sleep(10),
+ undefined = process_info(Ref, status),
+
+ {ok, Ref2} = odbc:connect(?RDBMS:connection_string(), []),
+ %% Serious programming fault, connetion will be shut down
+ gen_server:cast(Ref2, {self(), foobar, 10}),
+ test_server:sleep(10),
+ undefined = process_info(Ref2, status),
+
+ {ok, Ref3} = odbc:connect(?RDBMS:connection_string(), []),
+ %% Could be an innocent misstake the connection lives.
+ Ref3 ! foobar,
+ test_server:sleep(10),
+ {status, _} = process_info(Ref3, status),
+ ok.
+
diff --git a/lib/odbc/test/odbc_data_type_SUITE.erl b/lib/odbc/test/odbc_data_type_SUITE.erl
new file mode 100644
index 0000000000..7d4a0ca15f
--- /dev/null
+++ b/lib/odbc/test/odbc_data_type_SUITE.erl
@@ -0,0 +1,1498 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(odbc_data_type_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("test_server_line.hrl").
+-include("odbc_test.hrl").
+
+%%--------------------------------------------------------------------
+%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
+%% Arg - doc | suite
+%% Doc - string()
+%% Case - atom()
+%% Name of a test case function.
+%% Comment - string()
+%% Description: Returns documentation/test cases in this test suite
+%% or a skip tuple if the platform is not supported.
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Tests data types"];
+all(suite) ->
+ case odbc_test_lib:odbc_check() of
+ ok -> all();
+ Other -> {skip,Other}
+ end.
+
+all() ->
+ [char, int, floats, dec_and_num, timestamp].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ application:start(odbc),
+ [{tableName, odbc_test_lib:unique_table_name()} | Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ application:stop(odbc),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ case atom_to_list(Case) of
+ "binary" ++ _ ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{binary_strings, on}]);
+ "unicode" ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{binary_strings, on}]);
+ _ ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [])
+ end,
+ Dog = test_server:timetrap(?default_timeout),
+ Temp = lists:keydelete(connection_ref, 1, Config),
+ NewConfig = lists:keydelete(watchdog, 1, Temp),
+ [{watchdog, Dog}, {connection_ref, Ref} | NewConfig].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, Config) ->
+ Ref = ?config(connection_ref, Config),
+ ok = odbc:disconnect(Ref),
+ %% Clean up if needed
+ Table = ?config(tableName, Config),
+ {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), []),
+ odbc:sql_query(NewRef, "DROP TABLE " ++ Table),
+ odbc:disconnect(NewRef),
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+char(doc) ->
+ ["Tests char data types"];
+
+char(suite) ->
+ [char_fixed_lower_limit, char_fixed_upper_limit,
+ char_fixed_padding, varchar_lower_limit, varchar_upper_limit,
+ varchar_no_padding, text_lower_limit, text_upper_limit, unicode
+ ].
+
+char_fixed_lower_limit(doc) ->
+ ["Tests fixed length char data type lower boundaries."];
+char_fixed_lower_limit(suite) ->
+ [];
+char_fixed_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Below limit
+ {error, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ (?RDBMS:fixed_char_min() - 1))),
+ %% Lower limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_min())),
+
+ %% Right length data
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:fixed_char_min())
+ ++ "')"),
+ %% Select data
+ {selected, Fields,[{"a"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:fixed_char_min()
+ + 1))
+ ++ "')"),
+ ok.
+%%-------------------------------------------------------------------------
+
+char_fixed_upper_limit(doc) ->
+ ["Tests fixed length char data type upper boundaries."];
+char_fixed_upper_limit(suite) ->
+ [];
+char_fixed_upper_limit(Config) when is_list(Config) ->
+
+ case ?RDBMS of
+ postgres ->
+ {skip, "Limit unknown"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Upper limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:fixed_char_max())
+ ++ "')"),
+ %% Select data
+ {selected, Fields, [{CharStr}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = length(CharStr) == ?RDBMS:fixed_char_max(),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:fixed_char_max()
+ + 1))
+ ++ "')"),
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ %% Above limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ (?RDBMS:fixed_char_max() + 1))),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+char_fixed_padding(doc) ->
+ ["Tests that data that is shorter than the given size is padded "
+ "with blanks."];
+char_fixed_padding(suite) ->
+ [];
+char_fixed_padding(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Data should be padded with blanks
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_max())),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:fixed_char_min())
+ ++ "')"),
+
+ {selected, Fields, [{CharStr}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = length(CharStr) == ?RDBMS:fixed_char_max(),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+%%-------------------------------------------------------------------------
+
+varchar_lower_limit(doc) ->
+ ["Tests variable length char data type lower boundaries."];
+varchar_lower_limit(suite) ->
+ [];
+varchar_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Below limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_min() - 1)),
+ %% Lower limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_min())),
+
+ %% Right length data
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:var_char_min())
+ ++ "')"),
+ %% Select data
+ {selected, Fields, [{"a"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:var_char_min()+1))
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+varchar_upper_limit(doc) ->
+ ["Tests variable length char data type upper boundaries."];
+varchar_upper_limit(suite) ->
+ [];
+varchar_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ case ?RDBMS of
+ oracle ->
+ {skip, "Known bug in database"};
+ postgres ->
+ {skip, "Limit unknown"};
+ _ ->
+ %% Upper limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:var_char_max())
+ ++ "')"),
+
+ {selected, Fields, [{CharStr}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = length(CharStr) == ?RDBMS:var_char_max(),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:var_char_max()+1))
+ ++ "')"),
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ %% Above limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ (?RDBMS:var_char_max() + 1))),
+ ok
+ end.
+%%-------------------------------------------------------------------------
+
+varchar_no_padding(doc) ->
+ ["Tests that data that is shorter than the given max size is not padded "
+ "with blanks."];
+varchar_no_padding(suite) ->
+ [];
+varchar_no_padding(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Data should NOT be padded with blanks
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:var_char_min())
+ ++ "')"),
+
+ {selected, Fields, [{CharStr}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = length(CharStr) /= ?RDBMS:var_char_max(),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+text_lower_limit(doc) ->
+ ["Tests 'long' char data type lower boundaries."];
+text_lower_limit(suite) ->
+ [];
+text_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_text_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:text_min())
+ ++ "')"),
+
+ {selected, Fields, [{"a"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+text_upper_limit(doc) ->
+ [];
+text_upper_limit(suite) ->
+ [];
+text_upper_limit(Config) when is_list(Config) ->
+
+ {skip,"Consumes too much resources" }.
+%% Ref = ?config(connection_ref, Config),
+%% Table = ?config(tableName, Config),
+
+%% {updated, _} = % Value == 0 || -1 driver dependent!
+%% odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+%% ?RDBMS:create_text_table()),
+%% {updated, _} =
+%% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+%% "'" ++ string:chars($a, ?RDBMS:text_max())
+%% ++ "')"),
+
+%% {selected, Fields, [{CharStr}]} =
+%% odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+%% length(CharStr) == ?RDBMS:text_max(),
+%% ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+%% {error, _} =
+%% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+%% "'" ++ string:chars($a, (?RDBMS:text_max()+1))
+%% ++ "')"),
+%% ok.
+
+%%-------------------------------------------------------------------------
+binary_char(doc) ->
+ ["Tests char data types returned as erlang binaries"];
+
+binary_char(suite) ->
+ [binary_char_fixed_lower_limit, binary_char_fixed_upper_limit,
+ binary_char_fixed_padding, binary_varchar_lower_limit, binary_varchar_upper_limit,
+ binary_varchar_no_padding, binary_text_lower_limit, binary_text_upper_limit, unicode
+ ].
+
+binary_char_fixed_lower_limit(doc) ->
+ ["Tests fixed length char data type lower boundaries."];
+binary_char_fixed_lower_limit(suite) ->
+ [];
+binary_char_fixed_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Below limit
+ {error, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ (?RDBMS:fixed_char_min() - 1))),
+ %% Lower limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_min())),
+
+ %% Right length data
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:fixed_char_min())
+ ++ "')"),
+ %% Select data
+ {selected, Fields,[{<<"a">>}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:fixed_char_min()
+ + 1))
+ ++ "')"),
+ ok.
+%%-------------------------------------------------------------------------
+
+binary_char_fixed_upper_limit(doc) ->
+ ["Tests fixed length char data type upper boundaries."];
+binary_char_fixed_upper_limit(suite) ->
+ [];
+binary_char_fixed_upper_limit(Config) when is_list(Config) ->
+
+ case ?RDBMS of
+ postgres ->
+ {skip, "Limit unknown"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Upper limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:fixed_char_max())
+ ++ "')"),
+ %% Select data
+ {selected, Fields, [{CharBin}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = size(CharBin) == ?RDBMS:fixed_char_max(),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:fixed_char_max()
+ + 1))
+ ++ "')"),
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ %% Above limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ (?RDBMS:fixed_char_max() + 1))),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+binary_char_fixed_padding(doc) ->
+ ["Tests that data that is shorter than the given size is padded "
+ "with blanks."];
+binary_char_fixed_padding(suite) ->
+ [];
+binary_char_fixed_padding(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Data should be padded with blanks
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_max())),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:fixed_char_min())
+ ++ "')"),
+
+ {selected, Fields, [{CharBin}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = size(CharBin) == ?RDBMS:fixed_char_max(),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+%%-------------------------------------------------------------------------
+
+binary_varchar_lower_limit(doc) ->
+ ["Tests variable length char data type lower boundaries."];
+binary_varchar_lower_limit(suite) ->
+ [];
+binary_varchar_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Below limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_min() - 1)),
+ %% Lower limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_min())),
+
+ %% Right length data
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:var_char_min())
+ ++ "')"),
+ %% Select data
+ {selected, Fields, [{<<"a">>}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:var_char_min()+1))
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+binary_varchar_upper_limit(doc) ->
+ ["Tests variable length char data type upper boundaries."];
+binary_varchar_upper_limit(suite) ->
+ [];
+binary_varchar_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ case ?RDBMS of
+ oracle ->
+ {skip, "Known bug in database"};
+ postgres ->
+ {skip, "Limit unknown"};
+ _ ->
+ %% Upper limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:var_char_max())
+ ++ "')"),
+
+ {selected, Fields, [{CharBin}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = size(CharBin) == ?RDBMS:var_char_max(),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:var_char_max()+1))
+ ++ "')"),
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ %% Above limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ (?RDBMS:var_char_max() + 1))),
+ ok
+ end.
+%%-------------------------------------------------------------------------
+
+binary_varchar_no_padding(doc) ->
+ ["Tests that data that is shorter than the given max size is not padded "
+ "with blanks."];
+binary_varchar_no_padding(suite) ->
+ [];
+binary_varchar_no_padding(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Data should NOT be padded with blanks
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:var_char_min())
+ ++ "')"),
+
+ {selected, Fields, [{CharBin}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = size(CharBin) /= ?RDBMS:var_char_max(),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+binary_text_lower_limit(doc) ->
+ ["Tests 'long' char data type lower boundaries."];
+binary_text_lower_limit(suite) ->
+ [];
+binary_text_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_text_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:text_min())
+ ++ "')"),
+
+ {selected, Fields, [{<<"a">>}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+binary_text_upper_limit(doc) ->
+ [];
+binary_text_upper_limit(suite) ->
+ [];
+binary_text_upper_limit(Config) when is_list(Config) ->
+
+ {skip,"Consumes too much resources" }.
+%% Ref = ?config(connection_ref, Config),
+%% Table = ?config(tableName, Config),
+
+%% {updated, _} = % Value == 0 || -1 driver dependent!
+%% odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+%% ?RDBMS:create_text_table()),
+%% {updated, _} =
+%% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+%% "'" ++ string:chars($a, ?RDBMS:text_max())
+%% ++ "')"),
+
+%% {selected, Fields, [{CharBin}]} =
+%% odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+%% size(CharBin) == ?RDBMS:text_max(),
+%% ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+%% {error, _} =
+%% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+%% "'" ++ string:chars($a, (?RDBMS:text_max()+1))
+%% ++ "')"),
+%% ok.
+
+
+%%-------------------------------------------------------------------------
+
+int(doc) ->
+ ["Tests integer data types"];
+
+int(suite) ->
+ [tiny_int_lower_limit, tiny_int_upper_limit, small_int_lower_limit,
+ small_int_upper_limit, int_lower_limit, int_upper_limit,
+ big_int_lower_limit, big_int_upper_limit, bit_false, bit_true].
+
+%%-------------------------------------------------------------------------
+
+tiny_int_lower_limit(doc) ->
+ ["Tests integer of type tinyint."];
+tiny_int_lower_limit(suite) ->
+ [];
+tiny_int_lower_limit(Config) when is_list(Config) ->
+ case ?RDBMS of
+ postgres ->
+ {skip, "Type tiniyint not supported"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_tiny_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:tiny_int_min())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:tiny_int_min_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:tiny_int_min()
+ - 1)
+ ++ "')"),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+tiny_int_upper_limit(doc) ->
+ ["Tests integer of type tinyint."];
+tiny_int_upper_limit(suite) ->
+ [];
+tiny_int_upper_limit(Config) when is_list(Config) ->
+ case ?RDBMS of
+ postgres ->
+ {skip, "Type tiniyint not supported"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_tiny_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:tiny_int_max())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:tiny_int_max_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:tiny_int_max()
+ + 1)
+ ++ "')"),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+small_int_lower_limit(doc) ->
+ ["Tests integer of type smallint."];
+small_int_lower_limit(suite) ->
+ [];
+small_int_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_small_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:small_int_min())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:small_int_min_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:small_int_min()
+ - 1)
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+small_int_upper_limit(doc) ->
+ ["Tests integer of type smallint."];
+small_int_upper_limit(suite) ->
+ [];
+small_int_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_small_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:small_int_max())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:small_int_max_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:small_int_max()
+ + 1)
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+int_lower_limit(doc) ->
+ ["Tests integer of type int."];
+int_lower_limit(suite) ->
+ [];
+int_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:int_min())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:int_min_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:int_min() - 1)
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+int_upper_limit(doc) ->
+ ["Tests integer of type int."];
+int_upper_limit(suite) ->
+ [];
+int_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:int_max())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:int_max_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:int_max() + 1)
+ ++ "')"),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+big_int_lower_limit(doc) ->
+ ["Tests integer of type bigint"];
+big_int_lower_limit(suite) ->
+ [];
+big_int_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_big_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:big_int_min())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:big_int_min_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:big_int_min()
+ - 1)
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+big_int_upper_limit(doc) ->
+ ["Tests integer of type bigint."];
+big_int_upper_limit(suite) ->
+ [];
+big_int_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_big_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:big_int_max())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:big_int_max_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:big_int_max()
+ + 1)
+ ++ "')"),
+ ok.
+%%-------------------------------------------------------------------------
+
+bit_false(doc) ->
+ [""];
+bit_false(suite) ->
+ [];
+bit_false(Config) when is_list(Config) ->
+ case ?RDBMS of
+ oracle ->
+ {skip, "Not supported by driver"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_bit_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:bit_false())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:bit_false_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(-1)
+ ++ "')"),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+bit_true(doc) ->
+ [""];
+bit_true(suite) ->
+ [];
+bit_true(Config) when is_list(Config) ->
+ case ?RDBMS of
+ oracle ->
+ {skip, "Not supported by driver"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_bit_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:bit_true())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:bit_true_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(-1)
+ ++ "')"),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+floats(doc) ->
+ ["Test the datatype float."];
+floats(suite) ->
+ [float_lower_limit, float_upper_limit, float_zero, real_zero].
+
+%%-------------------------------------------------------------------------
+float_lower_limit(doc) ->
+ [""];
+float_lower_limit(suite) ->
+ [];
+float_lower_limit(Config) when is_list(Config) ->
+
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_float_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ float_to_list(
+ ?RDBMS:float_min())
+ ++ "')"),
+ {selected,[_ColName],[{MinFloat}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ true = ?RDBMS:float_min() == MinFloat,
+
+ case ?RDBMS of
+ oracle ->
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_float_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "INSERT INTO " ++ Table ++" VALUES(" ++
+ ?RDBMS:float_underflow() ++ ")"),
+
+ SelectResult = ?RDBMS:float_zero_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table);
+ _ ->
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ ?RDBMS:float_underflow() ++ ")")
+ end,
+ ok.
+
+
+%%-------------------------------------------------------------------------
+float_upper_limit(doc) ->
+ [""];
+float_upper_limit(suite) ->
+ [];
+float_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_float_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ float_to_list(
+ ?RDBMS:float_max())
+ ++ "')"),
+
+
+ {selected,[_ColName],[{MaxFloat}]}
+ = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ true = ?RDBMS:float_max() == MaxFloat,
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ ?RDBMS:float_overflow() ++ ")"),
+ ok.
+
+%%-------------------------------------------------------------------------
+float_zero(doc) ->
+ ["Test the float value zero."];
+float_zero(suite) ->
+ [];
+float_zero(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_float_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES('0')"),
+
+ SelectResult = ?RDBMS:float_zero_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ok.
+%%-------------------------------------------------------------------------
+real_zero(doc) ->
+ ["Test the real value zero."];
+real_zero(suite) ->
+ [];
+real_zero(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ case ?RDBMS of
+ oracle ->
+ {skip, "Not supported in Oracle"};
+ _ ->
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_real_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES('0')"),
+
+ SelectResult = ?RDBMS:real_zero_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ok
+ end.
+%%-------------------------------------------------------------------------
+dec_and_num(doc) ->
+ ["Tests decimal and numeric datatypes."];
+dec_and_num(suite) ->
+ [dec_long, dec_double, dec_bignum, num_long, num_double, num_bignum].
+%%------------------------------------------------------------------------
+dec_long(doc) ->
+ [""];
+dec_long(suit) ->
+ [];
+dec_long(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (9,0))"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{2}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+%%------------------------------------------------------------------------
+dec_double(doc) ->
+ [""];
+dec_double(suit) ->
+ [];
+dec_double(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (10,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{2.00000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (15,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields1, [{2.00000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields1),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (15, 1))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields2, [{1.60000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields2),
+ ok.
+
+%%------------------------------------------------------------------------
+dec_bignum(doc) ->
+ [""];
+dec_bignum(suit) ->
+ [];
+dec_bignum(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (16,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{"2"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (16,1))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields1, [{"1.6"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields1),
+ ok.
+%%------------------------------------------------------------------------
+num_long(doc) ->
+ [""];
+num_long(suit) ->
+ [];
+num_long(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (9,0))"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.5)"),
+
+ {selected, Fields, [{2}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+%%------------------------------------------------------------------------
+num_double(doc) ->
+ [""];
+num_double(suit) ->
+ [];
+num_double(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (10,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{2.0000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (15,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields1, [{2.0000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields1),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (15,1))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields2, [{1.6000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields2),
+ ok.
+%%------------------------------------------------------------------------
+num_bignum(doc) ->
+ [""];
+num_bignum(suit) ->
+ [];
+num_bignum(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (16,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{"2"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (16,1))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields1, [{"1.6"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields1),
+ ok.
+
+%%------------------------------------------------------------------------
+unicode(doc) ->
+ ["Test unicode support"];
+unicode(suit) ->
+ [];
+unicode(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_unicode_table()),
+
+ Latin1Data = ["���������",
+ "testasdf",
+ "Row 3",
+ "Row 4",
+ "Row 5",
+ "Row 6",
+ "Row 7",
+ "Row 8",
+ "Row 9",
+ "Row 10",
+ "Row 11",
+ "Row 12"],
+
+ case ?RDBMS of
+ sqlserver ->
+ w_char_support_win(Ref, Table, Latin1Data);
+ postgres ->
+ direct_utf8(Ref, Table, Latin1Data);
+ oracle ->
+ {skip, "not currently supported"}
+ end.
+
+w_char_support_win(Ref, Table, Latin1Data) ->
+ UnicodeIn = lists:map(fun(S) ->
+ unicode:characters_to_binary(S,latin1,{utf16,little})
+ end,
+ Latin1Data),
+
+ test_server:format("UnicodeIn (utf 16): ~p ~n",[UnicodeIn]),
+
+ {updated, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++ "(FIELD) values(?)",
+ [{{sql_wvarchar,50},UnicodeIn}]),
+
+ {selected,_,UnicodeOut} = odbc:sql_query(Ref,"SELECT * FROM " ++ Table),
+
+ test_server:format("UnicodeOut: ~p~n", [UnicodeOut]),
+
+ Result = lists:map(fun({Unicode}) ->
+ unicode:characters_to_list(Unicode,{utf16,little})
+ end,
+ UnicodeOut),
+ Latin1Data = Result.
+
+
+direct_utf8(Ref, Table, Latin1Data) ->
+ UnicodeIn = lists:map(fun(String) ->
+ unicode:characters_to_binary(String,latin1,utf8)
+ end,
+ Latin1Data),
+
+ test_server:format("UnicodeIn: ~p ~n",[UnicodeIn]),
+ {updated, _} = odbc:param_query(Ref,"INSERT INTO " ++ Table ++ "(FIELD) values(?)",
+ [{{sql_varchar,50}, UnicodeIn}]),
+
+ {selected,_,UnicodeOut} = odbc:sql_query(Ref,"SELECT * FROM " ++ Table),
+
+ test_server:format("UnicodeOut: ~p~n", [UnicodeOut]),
+
+ Result = lists:map(fun({Char}) ->
+ unicode:characters_to_list(Char,utf8)
+ end, UnicodeOut),
+
+ test_server:format("Result: ~p ~n", [Result]),
+
+ Latin1Data = Result.
+
+%%------------------------------------------------------------------------
+timestamp(doc) ->
+ [""];
+timestamp(suit) ->
+ [];
+timestamp(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_timestamp_table()),
+
+ Data = [calendar:local_time(),
+ {{2009,6,17},{20,54,59}},
+ {{2009,6,18},{20,54,59}},
+ {{2009,6,19},{20,54,59}},
+ {{2009,6,20},{20,54,59}},
+ {{2009,6,21},{20,54,59}}],
+
+ {updated, _} = odbc:param_query(Ref,"INSERT INTO " ++ Table ++ "(FIELD) values(?)",
+ [{sql_timestamp,Data}]),
+
+ %%% Crate list or database table rows
+ TimeStamps = lists:map(fun(Value) -> {Value} end, Data),
+
+ {selected,_, TimeStamps} = odbc:sql_query(Ref, "SELECT * FROM " ++ Table).
diff --git a/lib/odbc/test/odbc_query_SUITE.erl b/lib/odbc/test/odbc_query_SUITE.erl
new file mode 100644
index 0000000000..12b39be3b7
--- /dev/null
+++ b/lib/odbc/test/odbc_query_SUITE.erl
@@ -0,0 +1,1453 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(odbc_query_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("odbc_test.hrl").
+
+%%--------------------------------------------------------------------
+%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
+%% Arg - doc | suite
+%% Doc - string()
+%% Case - atom()
+%% Name of a test case function.
+%% Comment - string()
+%% Description: Returns documentation/test cases in this test suite
+%% or a skip tuple if the platform is not supported.
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Tests SQL queries"];
+all(suite) ->
+ case odbc_test_lib:odbc_check() of
+ ok -> all();
+ Other -> {skip, Other}
+ end.
+
+all() ->
+ [sql_query, first, last, next, prev, select_count,select_next,
+ select_relative, select_absolute, create_table_twice,
+ delete_table_twice, duplicate_key, not_connection_owner,
+ no_result_set, query_error, multiple_select_result_sets,
+ multiple_mix_result_sets, multiple_result_sets_error,
+ parameterized_queries, describe_table,
+ delete_nonexisting_row].
+
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) when is_list(Config) ->
+ application:start(odbc),
+ [{tableName, odbc_test_lib:unique_table_name()}| Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ application:stop(odbc),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(_Case, Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Dog = test_server:timetrap(?default_timeout),
+ Temp = lists:keydelete(connection_ref, 1, Config),
+ NewConfig = lists:keydelete(watchdog, 1, Temp),
+ [{watchdog, Dog}, {connection_ref, Ref} | NewConfig].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_Case, Config) ->
+ Ref = ?config(connection_ref, Config),
+ ok = odbc:disconnect(Ref),
+ %% Clean up if needed
+ Table = ?config(tableName, Config),
+ {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), []),
+ odbc:sql_query(NewRef, "DROP TABLE " ++ Table),
+ odbc:disconnect(NewRef),
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+sql_query(doc)->
+ ["Test the common cases"];
+sql_query(suite) -> [];
+sql_query(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+
+ {updated, Count} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ true = odbc_test_lib:check_row_count(1, Count),
+
+ InsertResult = ?RDBMS:insert_result(),
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {updated, NewCount} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ true = odbc_test_lib:check_row_count(1, NewCount),
+
+ UpdateResult = ?RDBMS:update_result(),
+ UpdateResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {updated, NewCount1} = odbc:sql_query(Ref, "DELETE FROM " ++ Table ++
+ " WHERE ID = 1"),
+
+ true = odbc_test_lib:check_row_count(1, NewCount1),
+
+ {selected, Fields, []} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["ID","DATA"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+select_count(doc) ->
+ ["Tests select_count/[2,3]'s timeout, "
+ " select_count's functionality will be better tested by other tests "
+ " such as first."];
+select_count(sute) -> [];
+select_count(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {ok, _} =
+ odbc:select_count(Ref, "SELECT * FROM " ++ Table, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} =
+ (catch odbc:select_count(Ref, "SELECT * FROM ", -1)),
+ ok.
+%%-------------------------------------------------------------------------
+first(doc) ->
+ ["Tests first/[1,2]"];
+first(suite) -> [];
+first(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {updated, NewCount} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+
+ FirstResult = ?RDBMS:selected_ID(1, first),
+ FirstResult = odbc:first(Ref),
+ FirstResult = odbc:first(Ref, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} = (catch odbc:first(Ref, -1)),
+ ok.
+
+%%-------------------------------------------------------------------------
+last(doc) ->
+ ["Tests last/[1,2]"];
+last(suite) -> [];
+last(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {updated, NewCount} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ LastResult = ?RDBMS:selected_ID(2, last),
+ LastResult = odbc:last(Ref),
+
+ LastResult = odbc:last(Ref, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} = (catch odbc:last(Ref, -1)),
+ ok.
+
+%%-------------------------------------------------------------------------
+next(doc) ->
+ ["Tests next/[1,2]"];
+next(suite) -> [];
+next(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {updated, NewCount} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ NextResult = ?RDBMS:selected_ID(1, next),
+ NextResult = odbc:next(Ref),
+ NextResult2 = ?RDBMS:selected_ID(2, next),
+ NextResult2 = odbc:next(Ref, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} = (catch odbc:next(Ref, -1)),
+ ok.
+%%-------------------------------------------------------------------------
+prev(doc) ->
+ ["Tests prev/[1,2]"];
+prev(suite) -> [];
+prev(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {updated, NewCount} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ odbc:last(Ref), % Position cursor last so there will be a prev
+ PrevResult = ?RDBMS:selected_ID(1, prev),
+ PrevResult = odbc:prev(Ref),
+
+ odbc:last(Ref), % Position cursor last so there will be a prev
+ PrevResult = odbc:prev(Ref, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} = (catch odbc:prev(Ref, -1)),
+ ok.
+%%-------------------------------------------------------------------------
+select_next(doc) ->
+ ["Tests select/[4,5] with CursorRelation = next "];
+select_next(suit) -> [];
+select_next(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(3)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(4)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(5)"),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ SelectResult1 = ?RDBMS:selected_next_N(1),
+ SelectResult1 = odbc:select(Ref, next, 3),
+
+ %% Test that selecting stops at the end of the result set
+ SelectResult2 = ?RDBMS:selected_next_N(2),
+ SelectResult2 = odbc:select(Ref, next, 3, ?TIMEOUT),
+ {'EXIT',{function_clause, _}} =
+ (catch odbc:select(Ref, next, 2, -1)),
+
+ %% If you try fetching data beyond the the end of result set,
+ %% you get an empty list.
+ {selected, Fields, []} = odbc:select(Ref, next, 1),
+
+ ["ID"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+select_relative(doc) ->
+ ["Tests select/[4,5] with CursorRelation = relative "];
+select_relative(suit) -> [];
+select_relative(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(3)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(4)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(5)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(6)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(7)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(8)"),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ SelectResult1 = ?RDBMS:selected_relative_N(1),
+ SelectResult1 = odbc:select(Ref, {relative, 2}, 3),
+
+ %% Test that selecting stops at the end of the result set
+ SelectResult2 = ?RDBMS:selected_relative_N(2),
+ SelectResult2 = odbc:select(Ref, {relative, 3}, 3, ?TIMEOUT),
+ {'EXIT',{function_clause, _}} =
+ (catch odbc:select(Ref, {relative, 3} , 2, -1)),
+ ok.
+
+%%-------------------------------------------------------------------------
+select_absolute(doc) ->
+ ["Tests select/[4,5] with CursorRelation = absolute "];
+select_absolute(suit) -> [];
+select_absolute(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(3)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(4)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(5)"),
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ SelectResult1 = ?RDBMS:selected_absolute_N(1),
+ SelectResult1 = odbc:select(Ref, {absolute, 1}, 3),
+
+ %% Test that selecting stops at the end of the result set
+ SelectResult2 = ?RDBMS:selected_absolute_N(2),
+ SelectResult2 = odbc:select(Ref, {absolute, 1}, 6, ?TIMEOUT),
+ {'EXIT',{function_clause, _}} =
+ (catch odbc:select(Ref, {absolute, 1}, 2, -1)),
+ ok.
+
+%%-------------------------------------------------------------------------
+create_table_twice(doc) ->
+ ["Test what happens if you try to create the same table twice."];
+create_table_twice(suite) -> [];
+create_table_twice(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+ {error, Error} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+ is_driver_error(Error),
+ ok.
+
+%%-------------------------------------------------------------------------
+delete_table_twice(doc) ->
+ ["Test what happens if you try to delete the same table twice."];
+delete_table_twice(suite) -> [];
+delete_table_twice(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+ {updated, _} = odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+ {error, Error} = odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+ is_driver_error(Error),
+ ok.
+
+%-------------------------------------------------------------------------
+duplicate_key(doc) ->
+ ["Test what happens if you try to use the same key twice"];
+duplicate_key(suit) -> [];
+duplicate_key(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA char(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {error, Error} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'foo')"),
+ is_driver_error(Error),
+ ok.
+
+%%-------------------------------------------------------------------------
+not_connection_owner(doc) ->
+ ["Test what happens if a process that did not start the connection"
+ " tries to acess it."];
+not_connection_owner(suite) -> [];
+not_connection_owner(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ spawn_link(?MODULE, not_owner, [self(), Ref, Table]),
+
+ receive
+ continue ->
+ ok
+ end.
+
+not_owner(Pid, Ref, Table) ->
+ {error, process_not_owner_of_odbc_connection} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer)"),
+
+ {error, process_not_owner_of_odbc_connection} =
+ odbc:disconnect(Ref),
+
+ Pid ! continue.
+
+%%-------------------------------------------------------------------------
+no_result_set(doc) ->
+ ["Tests what happens if you try to use a function that needs an "
+ "associated result set when there is none."];
+no_result_set(suite) -> [];
+no_result_set(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+
+ {error, result_set_does_not_exist} = odbc:first(Ref),
+ {error, result_set_does_not_exist} = odbc:last(Ref),
+ {error, result_set_does_not_exist} = odbc:next(Ref),
+ {error, result_set_does_not_exist} = odbc:prev(Ref),
+ {error, result_set_does_not_exist} = odbc:select(Ref, next, 1),
+ {error, result_set_does_not_exist} =
+ odbc:select(Ref, {absolute, 2}, 1),
+ {error, result_set_does_not_exist} =
+ odbc:select(Ref, {relative, 2}, 1),
+ ok.
+%%-------------------------------------------------------------------------
+query_error(doc) ->
+ ["Test what happens if there is an error in the query."];
+query_error(suite) ->
+ [];
+query_error(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA char(10), PRIMARY KEY(ID))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT ONTO " ++ Table ++ " VALUES(1,'bar')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+multiple_select_result_sets(doc) ->
+ ["Test what happens if you have a batch of select queries."];
+multiple_select_result_sets(suite) ->
+ [];
+multiple_select_result_sets(Config) when is_list(Config) ->
+ case ?RDBMS of
+ sqlserver ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), "
+ "PRIMARY KEY(ID))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1,'bar')"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2, 'foo')"),
+
+ MultipleResult = ?RDBMS:multiple_select(),
+
+ MultipleResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table ++
+ "; SELECT DATA FROM "++ Table ++
+ " WHERE ID=2"),
+ ok;
+ _ ->
+ {skip, "multiple result_set not supported"}
+ end.
+
+%%-------------------------------------------------------------------------
+multiple_mix_result_sets(doc) ->
+ ["Test what happens if you have a batch of select and other type of"
+ " queries."];
+multiple_mix_result_sets(suite) ->
+ [];
+multiple_mix_result_sets(Config) when is_list(Config) ->
+ case ?RDBMS of
+ sqlserver ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), "
+ "PRIMARY KEY(ID))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1,'bar')"),
+
+ MultipleResult = ?RDBMS:multiple_mix(),
+
+ MultipleResult =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2,'foo'); UPDATE " ++ Table ++
+ " SET DATA = 'foobar' WHERE ID =1;SELECT "
+ "* FROM "
+ ++ Table ++ ";DELETE FROM " ++ Table ++
+ " WHERE ID =1; SELECT DATA FROM " ++ Table),
+ ok;
+ _ ->
+ {skip, "multiple result_set not supported"}
+ end.
+%%-------------------------------------------------------------------------
+multiple_result_sets_error(doc) ->
+ ["Test what happens if one of the batched queries fails."];
+multiple_result_sets_error(suite) ->
+ [];
+multiple_result_sets_error(Config) when is_list(Config) ->
+ case ?RDBMS of
+ sqlserver ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), "
+ "PRIMARY KEY(ID))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1,'bar')"),
+
+ {error, Error} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1,'foo'); SELECT * FROM " ++ Table),
+ is_driver_error(Error),
+
+ {error, NewError} =
+ odbc:sql_query(Ref, "SELECT * FROM "
+ ++ Table ++ ";INSERT INTO " ++ Table ++
+ " VALUES(1,'foo')"),
+ is_driver_error(NewError),
+ ok;
+ _ ->
+ {skip, "multiple result_set not supported"}
+ end.
+
+%%-------------------------------------------------------------------------
+parameterized_queries(doc)->
+ ["Tests diffrent variants of parameterized queries."];
+parameterized_queries(suite) ->
+ %% Note timestamps are inserted with param_query in odbc_data_type_SUITE
+ %% so no need to test this again.
+ [param_integers,
+ param_insert_decimal, param_insert_numeric,
+ param_insert_string,
+ param_insert_float, param_insert_real, param_insert_double,
+ param_insert_mix, param_update, param_delete, param_select].
+
+%%-------------------------------------------------------------------------
+param_integers(doc)->
+ ["Test insertion of integers by parameterized queries."];
+param_integers(suite) ->
+ [param_insert_tiny_int,
+ param_insert_small_int, param_insert_int, param_insert_integer].
+%%-------------------------------------------------------------------------
+param_insert_tiny_int(doc)->
+ ["Test insertion of tiny ints by parameterized queries."];
+param_insert_tiny_int(suite) ->
+ [];
+param_insert_tiny_int(Config) when is_list(Config) ->
+ case ?RDBMS of
+ sqlserver ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD TINYINT)"),
+
+ {updated, Count} =
+ odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_tinyint, [1, 2]}],
+ ?TIMEOUT),%Make sure to test timeout clause
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_tiny_int(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_tinyint, [1, "2"]}])),
+ ok;
+ _ ->
+ {skip, "Type tiniyint not supported"}
+ end.
+%%-------------------------------------------------------------------------
+param_insert_small_int(doc)->
+ ["Test insertion of small ints by parameterized queries."];
+param_insert_small_int(suite) ->
+ [];
+param_insert_small_int(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD SMALLINT)"),
+
+ {updated, Count} =
+ odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)", [{sql_smallint, [1, 2]}],
+ ?TIMEOUT), %% Make sure to test timeout clause
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_small_int(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_smallint, [1, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_int(doc)->
+ ["Test insertion of ints by parameterized queries."];
+param_insert_int(suite) ->
+ [];
+param_insert_int(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD INT)"),
+
+ Int = ?RDBMS:small_int_max() + 1,
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_integer, [1, Int]}]),
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_int(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_integer, [1, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_integer(doc)->
+ ["Test insertion of integers by parameterized queries."];
+param_insert_integer(suite) ->
+ [];
+param_insert_integer(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD INTEGER)"),
+
+ Int = ?RDBMS:small_int_max() + 1,
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_integer, [1, Int]}]),
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_int(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_integer, [1, 2.3]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_decimal(doc)->
+ ["Test insertion of decimal numbers by parameterized queries."];
+param_insert_decimal(suite) ->
+ [];
+param_insert_decimal(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD DECIMAL (3,0))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_decimal, 3, 0}, [1, 2]}]),
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_decimal(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_decimal, 3, 0}, [1, "2"]}])),
+
+
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD DECIMAL (3,1))"),
+
+ {updated, NewCount} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_decimal, 3, 1}, [0.25]}]),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+
+ {selected, Fields, [{Value}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ odbc_test_lib:match_float(Value, 0.3, 0.01),
+
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_numeric(doc)->
+ ["Test insertion of numeric numbers by parameterized queries."];
+param_insert_numeric(suite) ->
+ [];
+param_insert_numeric(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD NUMERIC (3,0))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_numeric,3,0}, [1, 2]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_numeric(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_decimal, 3, 0}, [1, "2"]}])),
+
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD NUMERIC (3,1))"),
+
+ {updated, NewCount} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_numeric, 3, 1}, [0.25]}]),
+
+ true = odbc_test_lib:check_row_count(1, NewCount),
+
+ {selected, Fileds, [{Value}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ odbc_test_lib:match_float(Value, 0.3, 0.01),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_string(doc) ->
+ ["Test insertion of strings by parameterized queries."];
+param_insert_string(suite) ->
+ [param_insert_char, param_insert_character, param_insert_char_varying,
+ param_insert_character_varying].
+
+%%-------------------------------------------------------------------------
+param_insert_char(doc)->
+ ["Test insertion of fixed length string by parameterized queries."];
+param_insert_char(suite) ->
+ [];
+param_insert_char(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD CHAR (10))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10},
+ ["foofoofoof", "0123456789"]}]),
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected,Fileds,[{"foofoofoof"}, {"0123456789"}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ {error, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10},
+ ["foo", "01234567890"]}]),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10}, ["1", 2.3]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_character(doc)->
+ ["Test insertion of fixed length string by parameterized queries."];
+param_insert_character(suite) ->
+ [];
+param_insert_character(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD CHARACTER (10))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10},
+ ["foofoofoof", "0123456789"]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{"foofoofoof"}, {"0123456789"}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ {error, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10},
+ ["foo", "01234567890"]}]),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10}, ["1", 2]}])),
+ ok.
+
+%%------------------------------------------------------------------------
+param_insert_char_varying(doc)->
+ ["Test insertion of variable length strings by parameterized queries."];
+param_insert_char_varying(suite) ->
+ [];
+param_insert_char_varying(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD CHAR VARYING(10))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10},
+ ["foo", "0123456789"]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{"foo"}, {"0123456789"}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ {error, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10},
+ ["foo", "01234567890"]}]),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10}, ["1", 2.3]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_character_varying(doc)->
+ ["Test insertion of variable length strings by parameterized queries."];
+param_insert_character_varying(suite) ->
+ [];
+param_insert_character_varying(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD CHARACTER VARYING(10))"),
+
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10},
+ ["foo", "0123456789"]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{"foo"}, {"0123456789"}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ {error, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10},
+ ["foo", "01234567890"]}]),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10}, ["1", 2]}])),
+ ok.
+%%-------------------------------------------------------------------------
+param_insert_float(doc)->
+ ["Test insertion of floats by parameterized queries."];
+param_insert_float(suite) ->
+ [];
+param_insert_float(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD FLOAT(5))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_float,5}, [1.3, 1.2]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{Float1},{Float2}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ case (odbc_test_lib:match_float(Float1, 1.3, 0.000001) and
+ odbc_test_lib:match_float(Float2, 1.2, 0.000001)) of
+ true ->
+ ok;
+ false ->
+ test_server:fail(float_numbers_do_not_match)
+ end,
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_float, 5}, [1.0, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_real(doc)->
+ ["Test insertion of real numbers by parameterized queries."];
+param_insert_real(suite) ->
+ [];
+param_insert_real(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD REAL)"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_real, [1.3, 1.2]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ %_InsertResult = ?RDBMS:param_select_real(),
+
+ {selected, Fileds, [{Real1},{Real2}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ case (odbc_test_lib:match_float(Real1, 1.3, 0.000001) and
+ odbc_test_lib:match_float(Real2, 1.2, 0.000001)) of
+ true ->
+ ok;
+ false ->
+ test_server:fail(real_numbers_do_not_match)
+ end,
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_real,[1.0, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_double(doc)->
+ ["Test insertion of doubles by parameterized queries."];
+param_insert_double(suite) ->
+ [];
+param_insert_double(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD DOUBLE PRECISION)"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_double, [1.3, 1.2]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{Double1},{Double2}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ case (odbc_test_lib:match_float(Double1, 1.3, 0.000001) and
+ odbc_test_lib:match_float(Double2, 1.2, 0.000001)) of
+ true ->
+ ok;
+ false ->
+ test_server:fail(double_numbers_do_not_match)
+ end,
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_double, [1.0, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_mix(doc)->
+ ["Test insertion of a mixture of datatypes by parameterized queries."];
+param_insert_mix(suite) ->
+ [];
+param_insert_mix(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID INTEGER, DATA CHARACTER VARYING(10),"
+ " PRIMARY KEY(ID))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2]},
+ {{sql_varchar, 10}, ["foo", "bar"]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_mix(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+ ok.
+%%-------------------------------------------------------------------------
+param_update(doc)->
+ ["Test parameterized update query."];
+param_update(suite) ->
+ [];
+param_update(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID INTEGER, DATA CHARACTER VARYING(10),"
+ " PRIMARY KEY(ID))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2, 3]},
+ {{sql_varchar, 10},
+ ["foo", "bar", "baz"]}]),
+
+ true = odbc_test_lib:check_row_count(3, Count),
+
+ {updated, NewCount} = odbc:param_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foobar' WHERE ID = ?",
+ [{sql_integer, [1, 2]}]),
+
+ true = odbc_test_lib:check_row_count(2, NewCount),
+
+ UpdateResult = ?RDBMS:param_update(),
+
+ UpdateResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+delete_nonexisting_row(doc) -> % OTP-5759
+ ["Make a delete...where with false conditions (0 rows deleted). ",
+ "This used to give an error message (see ticket OTP-5759)."];
+delete_nonexisting_row(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table
+ ++ " (ID INTEGER, DATA CHARACTER VARYING(10))"),
+ {updated, Count} =
+ odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2, 3]},
+ {{sql_varchar, 10}, ["foo", "bar", "baz"]}]),
+
+ true = odbc_test_lib:check_row_count(3, Count),
+
+ {updated, NewCount} =
+ odbc:sql_query(Ref, "DELETE FROM " ++ Table ++ " WHERE ID = 8"),
+
+ true = odbc_test_lib:check_row_count(0, NewCount),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "DROP TABLE "++ Table),
+
+ ok.
+
+%%-------------------------------------------------------------------------
+param_delete(doc) ->
+ ["Test parameterized delete query."];
+param_delete(suite) ->
+ [];
+param_delete(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID INTEGER, DATA CHARACTER VARYING(10),"
+ " PRIMARY KEY(ID))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2, 3]},
+ {{sql_varchar, 10},
+ ["foo", "bar", "baz"]}]),
+ true = odbc_test_lib:check_row_count(3, Count),
+
+ {updated, NewCount} = odbc:param_query(Ref, "DELETE FROM " ++ Table ++
+ " WHERE ID = ?",
+ [{sql_integer, [1, 2]}]),
+
+ true = odbc_test_lib:check_row_count(2, NewCount),
+
+ UpdateResult = ?RDBMS:param_delete(),
+
+ UpdateResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+param_select(doc) ->
+ ["Test parameterized select query."];
+param_select(suite) ->
+ [];
+param_select(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID INTEGER, DATA CHARACTER VARYING(10),"
+ " PRIMARY KEY(ID))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2, 3]},
+ {{sql_varchar, 10},
+ ["foo", "bar", "foo"]}]),
+
+ true = odbc_test_lib:check_row_count(3, Count),
+
+ SelectResult = ?RDBMS:param_select(),
+
+ SelectResult = odbc:param_query(Ref, "SELECT * FROM " ++ Table ++
+ " WHERE DATA = ?",
+ [{{sql_varchar, 10}, ["foo"]}]),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_table(doc) ->
+ ["Test describe_table/[2,3]"];
+describe_table(suite) ->
+ [describe_integer, describe_string, describe_floating, describe_dec_num,
+ describe_no_such_table].
+
+%%-------------------------------------------------------------------------
+describe_integer(doc) ->
+ ["Test describe_table/[2,3] for integer columns."];
+describe_integer(suite) ->
+ [];
+describe_integer(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (int1 SMALLINT, int2 INT, int3 INTEGER)"),
+
+ Decs = ?RDBMS:describe_integer(),
+ %% Make sure to test timeout clause
+ Decs = odbc:describe_table(Ref, Table, ?TIMEOUT),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_string(doc) ->
+ ["Test describe_table/[2,3] for string columns."];
+describe_string(suite) ->
+ [];
+describe_string(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (str1 char(10), str2 character(10), "
+ "str3 CHAR VARYING(10), str4 "
+ "CHARACTER VARYING(10))"),
+
+ Decs = ?RDBMS:describe_string(),
+
+ Decs = odbc:describe_table(Ref, Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_floating(doc) ->
+ ["Test describe_table/[2,3] for floting columns."];
+describe_floating(suite) ->
+ [];
+describe_floating(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (f FLOAT(5), r REAL, "
+ "d DOUBLE PRECISION)"),
+
+ Decs = ?RDBMS:describe_floating(),
+
+ Decs = odbc:describe_table(Ref, Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_dec_num(doc) ->
+ ["Test describe_table/[2,3] for decimal and numerical columns"];
+describe_dec_num(suite) ->
+ [];
+describe_dec_num(Config) when is_list(Config) ->
+
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (dec DECIMAL(9,3), num NUMERIC(9,2))"),
+
+ Decs = ?RDBMS:describe_dec_num(),
+
+ Decs = odbc:describe_table(Ref, Table),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+describe_timestamp(doc) ->
+ ["Test describe_table/[2,3] for tinmestap columns"];
+describe_timestamp(suite) ->
+ [];
+describe_timestamp(Config) when is_list(Config) ->
+
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_timestamp_table()),
+
+ Decs = ?RDBMS:describe_timestamp(),
+
+ Decs = odbc:describe_table(Ref, Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_no_such_table(doc) ->
+ ["Test what happens if you try to describe a table that does not exist."];
+describe_no_such_table(suite) ->
+ [];
+describe_no_such_table(Config) when is_list(Config) ->
+
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {error, _ } = odbc:describe_table(Ref, Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Internal functions
+%%-------------------------------------------------------------------------
+
+is_driver_error(Error) ->
+ case is_list(Error) of
+ true ->
+ test_server:format("Driver error ~p~n", [Error]),
+ ok;
+ false ->
+ test_server:fail(Error)
+ end.
diff --git a/lib/odbc/test/odbc_start_SUITE.erl b/lib/odbc/test/odbc_start_SUITE.erl
new file mode 100644
index 0000000000..2cca8e4546
--- /dev/null
+++ b/lib/odbc/test/odbc_start_SUITE.erl
@@ -0,0 +1,147 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(odbc_start_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("odbc_test.hrl").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initialization before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case code:which(odbc) of
+ non_existing ->
+ {skip, "No ODBC built"};
+ _ ->
+ [{tableName, odbc_test_lib:unique_table_name()} | Config]
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initialization before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initialization before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config0) ->
+ test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]),
+ Config = lists:keydelete(watchdog, 1, Config0),
+ Dog = test_server:timetrap(?TIMEOUT),
+ [{watchdog, Dog} | Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, Config) ->
+ Dog = ?config(watchdog, Config),
+ case Dog of
+ undefined ->
+ ok;
+ _ ->
+ test_server:timetrap_cancel(Dog)
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test start/stop of odbc"];
+
+all(suite) ->
+ case odbc_test_lib:odbc_check() of
+ ok -> all();
+ Other -> {skip, Other}
+ end.
+
+all() ->
+ [start].
+
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+
+start(doc) ->
+ ["Test start/stop of odbc"];
+start(suite) ->
+ [];
+start(Config) when is_list(Config) ->
+ {error,odbc_not_started} = odbc:connect(?RDBMS:connection_string(), []),
+ odbc:start(),
+ case odbc:connect(?RDBMS:connection_string(), []) of
+ {ok, Ref0} ->
+ ok = odbc:disconnect(Ref0),
+ odbc:stop(),
+ {error,odbc_not_started} =
+ odbc:connect(?RDBMS:connection_string(), []),
+ start_odbc(transient),
+ start_odbc(permanent);
+ {error, odbc_not_started} ->
+ test_server:fail(start_failed);
+ Error ->
+ test_server:format("Connection failed: ~p~n", [Error]),
+ {skip, "ODBC is not properly setup"}
+ end.
+
+start_odbc(Type) ->
+ ok = odbc:start(Type),
+ case odbc:connect(?RDBMS:connection_string(), []) of
+ {ok, Ref} ->
+ ok = odbc:disconnect(Ref),
+ odbc:stop();
+ {error, odbc_not_started} ->
+ test_server:fail(start_failed)
+ end.
diff --git a/lib/odbc/test/odbc_test.hrl b/lib/odbc/test/odbc_test.hrl
new file mode 100644
index 0000000000..87f50043db
--- /dev/null
+++ b/lib/odbc/test/odbc_test.hrl
@@ -0,0 +1,37 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+% Default timetrap timeout (set in init_per_testcase).
+% This should be set relatively high (10-15 times the expected
+% max testcasetime).
+-define(default_timeout, ?t:minutes(10)).
+
+-define(RDBMS, case os:type() of
+ {unix, sunos} ->
+ postgres;
+ {unix,linux} ->
+ postgres;
+ {win32, _} ->
+ sqlserver
+ end).
+
+-define(TIMEOUT, 100000).
+
+
diff --git a/lib/odbc/test/odbc_test_lib.erl b/lib/odbc/test/odbc_test_lib.erl
new file mode 100644
index 0000000000..92e895eb87
--- /dev/null
+++ b/lib/odbc/test/odbc_test_lib.erl
@@ -0,0 +1,77 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(odbc_test_lib).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("odbc_test.hrl").
+-include("test_server.hrl").
+
+unique_table_name() ->
+ lists:reverse(lists:foldl(fun($@, Acc) -> [$t, $A |Acc] ;
+ (X, Acc) -> [X |Acc] end,
+ [], atom_to_list(node()))).
+
+match_float(Float, Match, Delta) ->
+ (Float < Match + Delta) and (Float > Match - Delta).
+
+odbc_check() ->
+ case erlang:system_info(wordsize) of
+ 4 ->
+ case test_server:os_type() of
+ {unix, sunos} ->
+ ok;
+ {unix, linux} ->
+ ok;
+ {win32, _} ->
+ ok;
+ Other ->
+ lists:flatten(
+ io_lib:format("Platform not supported: ~w",
+ [Other]))
+ end;
+ Other ->
+ case test_server:os_type() of
+ {unix, linux} ->
+ ok;
+ Platform ->
+ lists:flatten(
+ io_lib:format("Word on platform ~w size"
+ " ~w not supported", [Other,
+ Platform]))
+ end
+ end.
+
+check_row_count(Count, Count) ->
+ test_server:format("Correct row count Count: ~p~n", [Count]),
+ true;
+check_row_count(_, undefined) ->
+ test_server:format("Undefined row count ~n", []),
+ true;
+check_row_count(Expected, Count) ->
+ test_server:format("Incorrect row count Expected ~p Got ~p~n",
+ [Expected, Count]),
+ false.
+
+to_upper(List) ->
+ lists:map(fun(Str) -> string:to_upper(Str) end, List).
diff --git a/lib/odbc/test/oracle.erl b/lib/odbc/test/oracle.erl
new file mode 100644
index 0000000000..ebf6dbb6bf
--- /dev/null
+++ b/lib/odbc/test/oracle.erl
@@ -0,0 +1,246 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(oracle).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+%-------------------------------------------------------------------------
+connection_string() ->
+ "DSN=Oracle8;UID=odbctest".
+
+%-------------------------------------------------------------------------
+insert_result() ->
+ {selected,["ID","DATA"],[{"1","bar"}]}.
+
+update_result() ->
+ {selected,["ID","DATA"],[{"1","foo"}]}.
+
+selected_ID(N, next) ->
+ {selected,["ID"],[{integer_to_list(N)}]};
+
+selected_ID(_, _) ->
+ {error, driver_does_not_support_function}.
+
+selected_next_N(1)->
+ {selected,["ID"],
+ [{"1"},
+ {"2"},
+ {"3"}]};
+
+selected_next_N(2)->
+ {selected,["ID"],
+ [{"4"},
+ {"5"}]}.
+
+selected_relative_N(_)->
+ {error, driver_does_not_support_function}.
+
+selected_absolute_N(_)->
+ {error, driver_does_not_support_function}.
+
+selected_list_rows() ->
+ {selected,["ID", "DATA"],[["1", "bar"],["2","foo"]]}.
+
+first_list_rows() ->
+ {error, driver_does_not_support_function}.
+last_list_rows() ->
+ {error, driver_does_not_support_function}.
+prev_list_rows() ->
+ {error, driver_does_not_support_function}.
+next_list_rows() ->
+ {selected,["ID","DATA"],[["1","bar"]]}.
+
+%% In case we get a better oracle driver that support this some day .....
+multiple_select()->
+ [{selected,["ID", "DATA"],[{"1", "bar"},{"2", "foo"}]},
+ {selected,["ID"],[{"foo"}]}].
+
+multiple_mix()->
+ [{updated, 1},{updated, 1},
+ {selected,["ID", "DATA"],[{"1", "foobar"},{"2", "foo"}]},
+ {updated, 1}, {selected,["DATA"],[{"foo"}]}].
+
+%-------------------------------------------------------------------------
+fixed_char_min() ->
+ 1.
+fixed_char_max() ->
+ 2000. %% Should be 255 acording to manual but empirical tests say 2000
+
+create_fixed_char_table(Size) ->
+ " (FIELD char(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+var_char_min() ->
+ 1.
+var_char_max() ->
+ 2000.
+
+create_var_char_table(Size) ->
+ " (FIELD varchar2(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+text_min() ->
+ 1.
+text_max() ->
+ 2147483646. % 2147483647. %% 2^31 - 1
+
+create_text_table() ->
+ " (FIELD long)". %Oracle long is variable length char data
+
+%-------------------------------------------------------------------------
+create_unicode_table() ->
+ " (FIELD nvarchar(50))".
+
+%-------------------------------------------------------------------------
+create_timestamp_table() ->
+ " (FIELD DATETIME)".
+
+%-------------------------------------------------------------------------
+tiny_int_min() ->
+ -999.
+tiny_int_max() ->
+ 999.
+
+create_tiny_int_table() ->
+ " (FIELD number(3, 0))".
+
+tiny_int_min_selected() ->
+ {selected,["FIELD"],[{-999}]}.
+
+tiny_int_max_selected() ->
+ {selected,["FIELD"], [{999}]}.
+
+%-------------------------------------------------------------------------
+small_int_min() ->
+ -99999.
+small_int_max() ->
+ 99999.
+
+create_small_int_table() ->
+ " (FIELD number(5, 0))".
+
+small_int_min_selected() ->
+ {selected,["FIELD"],[{-99999}]}.
+
+small_int_max_selected() ->
+ {selected,["FIELD"], [{99999}]}.
+
+%-------------------------------------------------------------------------
+int_min() ->
+ -999999999.
+int_max() ->
+ 999999999.
+
+create_int_table() ->
+ " (FIELD number(9, 0))".
+
+int_min_selected() ->
+ {selected,["FIELD"],[{-999999999}]}.
+
+int_max_selected() ->
+ {selected,["FIELD"], [{999999999}]}.
+
+%-------------------------------------------------------------------------
+big_int_min() ->
+ -99999999999999999999999999999999999999.
+
+big_int_max() ->
+ 99999999999999999999999999999999999999.
+
+create_big_int_table() ->
+ " (FIELD number(38,0))".
+
+big_int_min_selected() ->
+ {selected,["FIELD"], [{"-99999999999999999999999999999999999999"}]}.
+
+big_int_max_selected() ->
+ {selected,["FIELD"], [{"99999999999999999999999999999999999999"}]}.
+
+%-------------------------------------------------------------------------
+float_min() ->
+ 1.40129846432481707e-45.
+
+float_max() ->
+ 3.40282346638528860e+38.
+
+create_float_table() ->
+ " (FIELD float(32))".
+
+float_underflow() ->
+ "'4.94065645841246544e-324'".
+float_overflow() ->
+ "'1.79769313486231570e+308'".
+
+float_zero_selected() ->
+ {selected,["FIELD"],[{0.00000e+0}]}.
+
+%-------------------------------------------------------------------------
+param_select_small_int() ->
+ {selected,["FIELD"],[{"1"}, {"2"}]}.
+
+param_select_int() ->
+ Int = small_int_max() + 1,
+ {selected,["FIELD"],[{"1"}, {integer_to_list(Int)}]}.
+
+param_select_decimal() ->
+ {selected,["FIELD"],[{1},{2}]}.
+
+param_select_numeric() ->
+ {selected,["FIELD"],[{1},{2}]}.
+
+param_select_float() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_real() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_double() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_mix() ->
+ {selected,["ID","DATA"],[{"1", "foo"}, {"2", "bar"}]}.
+
+param_update() ->
+ {selected,["ID","DATA"],[{"1", "foobar"}, {"2", "foobar"}, {"3", "baz"}]}.
+
+param_delete() ->
+ {selected,["ID","DATA"],[{"3", "baz"}]}.
+
+param_select() ->
+ {selected,["ID","DATA"],[{"1", "foo"},{"3", "foo"}]}.
+
+%-------------------------------------------------------------------------
+describe_integer() ->
+ {ok,[{"INT1",{sql_decimal,38,0}},{"INT2",{sql_decimal,38,0}},
+ {"INT3",{sql_decimal,38,0}}]}.
+
+describe_string() ->
+ {ok,[{"STR1",{sql_char,10}},
+ {"STR2",{sql_char,10}},
+ {"STR3",{sql_varchar,10}},
+ {"STR4",{sql_varchar,10}}]}.
+
+describe_floating() ->
+ {ok,[{"F",sql_double},{"R",sql_double},{"D",sql_double}]}.
+describe_dec_num() ->
+ {ok,[{"DEC",{sql_decimal,9,3}},{"NUM",{sql_decimal,9,2}}]}.
diff --git a/lib/odbc/test/postgres.erl b/lib/odbc/test/postgres.erl
new file mode 100644
index 0000000000..169ca26e43
--- /dev/null
+++ b/lib/odbc/test/postgres.erl
@@ -0,0 +1,294 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(postgres).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+%-------------------------------------------------------------------------
+connection_string() ->
+ case test_server:os_type() of
+ {unix, sunos} ->
+ "DSN=Postgres;UID=odbctest";
+ {unix, linux} ->
+ Size = erlang:system_info(wordsize),
+ linux_dist_connection_string(Size)
+ end.
+
+linux_dist_connection_string(4) ->
+ case linux_dist() of
+ "ubuntu" ->
+ "DSN=PostgresLinuxUbuntu;UID=odbctest";
+ _ ->
+ "DSN=PostgresLinux;UID=odbctest"
+ end;
+
+linux_dist_connection_string(_) ->
+ "DSN=PostgresLinux64;UID=odbctest".
+
+linux_dist() ->
+ case file:read_file("/etc/issue") of
+ {ok, Binary} ->
+ [Dist | _ ] = string:tokens(binary_to_list(Binary), " "),
+ string:to_lower(Dist);
+ {error, _} ->
+ other
+ end.
+
+
+%-------------------------------------------------------------------------
+insert_result() ->
+ {selected,["id","data"],[{1,"bar"}]}.
+
+update_result() ->
+ {selected,["id","data"],[{1,"foo"}]}.
+
+selected_ID(N, next) ->
+ {selected,["id"],[{N}]};
+
+selected_ID(_, _) ->
+ {error, driver_does_not_support_function}.
+
+selected_next_N(1)->
+ {selected,["id"],
+ [{1},
+ {2},
+ {3}]};
+
+selected_next_N(2)->
+ {selected,["id"],
+ [{4},
+ {5}]}.
+
+selected_relative_N(_)->
+ {error, driver_does_not_support_function}.
+
+selected_absolute_N(_)->
+ {error, driver_does_not_support_function}.
+
+selected_list_rows() ->
+ {selected,["id", "data"],[[1, "bar"],[2,"foo"]]}.
+
+first_list_rows() ->
+ {error, driver_does_not_support_function}.
+last_list_rows() ->
+ {error, driver_does_not_support_function}.
+prev_list_rows() ->
+ {error, driver_does_not_support_function}.
+next_list_rows() ->
+ {selected,["id","data"],[[1,"bar"]]}.
+
+%% In case we get a better postgres driver that support this some day .....
+multiple_select()->
+ [{selected,["id", "data"],[{1, "bar"},{2, "foo"}]},
+ {selected,["id"],[{"foo"}]}].
+
+multiple_mix()->
+ [{updated, 1},{updated, 1},
+ {selected,["id", "data"],[{1, "foobar"},{2, "foo"}]},
+ {updated, 1}, {selected,["data"],[{"foo"}]}].
+
+%-------------------------------------------------------------------------
+fixed_char_min() ->
+ 1.
+fixed_char_max() ->
+ 2000.
+
+create_fixed_char_table(Size) ->
+ " (FIELD char(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+var_char_min() ->
+ 1.
+var_char_max() ->
+ 2000.
+
+create_var_char_table(Size) ->
+ " (FIELD varchar(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+text_min() ->
+ 1.
+text_max() ->
+ 2147483646. % 2147483647. %% 2^31 - 1
+
+create_text_table() ->
+ " (FIELD text)".
+
+%-------------------------------------------------------------------------
+create_unicode_table() ->
+ " (FIELD text)".
+
+%-------------------------------------------------------------------------
+create_timestamp_table() ->
+ " (FIELD TIMESTAMP)".
+
+%-------------------------------------------------------------------------
+small_int_min() ->
+ -32768.
+small_int_max() ->
+ 32767.
+
+create_small_int_table() ->
+ " (FIELD smallint)".
+
+small_int_min_selected() ->
+ {selected,["field"],[{-32768}]}.
+
+small_int_max_selected() ->
+ {selected,["field"], [{32767}]}.
+
+%-------------------------------------------------------------------------
+int_min() ->
+ -2147483648.
+int_max() ->
+ 2147483647.
+
+create_int_table() ->
+ " (FIELD int)".
+
+int_min_selected() ->
+ {selected,["field"],[{-2147483648}]}.
+
+int_max_selected() ->
+ {selected,["field"], [{2147483647}]}.
+
+%-------------------------------------------------------------------------
+big_int_min() ->
+ -9223372036854775808.
+
+big_int_max() ->
+ 9223372036854775807.
+
+create_big_int_table() ->
+ " (FIELD bigint )".
+
+big_int_min_selected() ->
+ {selected,["field"], [{"-9223372036854775808"}]}.
+
+big_int_max_selected() ->
+ {selected,["field"], [{"9223372036854775807"}]}.
+
+%-------------------------------------------------------------------------
+bit_false() ->
+ 0.
+bit_true() ->
+ 1.
+
+create_bit_table() ->
+ " (FIELD bit)".
+
+bit_false_selected() ->
+ {selected,["field"],[{"0"}]}.
+
+bit_true_selected() ->
+ {selected,["field"], [{"1"}]}.
+
+%-------------------------------------------------------------------------
+float_min() ->
+ 1.79e-307.
+float_max() ->
+ 1.79e+308.
+
+create_float_table() ->
+ " (FIELD float)".
+
+float_underflow() ->
+ "1.80e-308".
+float_overflow() ->
+ "1.80e+308".
+
+float_zero_selected() ->
+ {selected,["field"],[{0.00000e+0}]}.
+
+%-------------------------------------------------------------------------
+real_min() ->
+ -3.40e+38.
+real_max() ->
+ 3.40e+38.
+
+real_underflow() ->
+ "-3.41e+38".
+
+real_overflow() ->
+ "3.41e+38".
+
+create_real_table() ->
+ " (FIELD real)".
+
+real_zero_selected() ->
+ {selected,["field"],[{0.00000e+0}]}.
+
+%-------------------------------------------------------------------------
+param_select_small_int() ->
+ {selected,["field"],[{1}, {2}]}.
+
+param_select_int() ->
+ Int = small_int_max() + 1,
+ {selected,["field"],[{1}, {Int}]}.
+
+param_select_decimal() ->
+ {selected,["field"],[{1},{2}]}.
+
+param_select_numeric() ->
+ {selected,["field"],[{1},{2}]}.
+
+param_select_float() ->
+ {selected,["field"],[{1.30000},{1.20000}]}.
+
+param_select_real() ->
+ {selected,["field"],[{1.30000},{1.20000}]}.
+
+param_select_double() ->
+ {selected,["field"],[{1.30000},{1.20000}]}.
+
+param_select_mix() ->
+ {selected,["id","data"],[{1, "foo"}, {2, "bar"}]}.
+
+param_update() ->
+ {selected,["id","data"],[{3, "baz"},{1, "foobar"}, {2, "foobar"}]}.
+
+param_delete() ->
+ {selected,["id","data"],[{3, "baz"}]}.
+
+param_select() ->
+ {selected,["id","data"],[{1, "foo"},{3, "foo"}]}.
+
+%-------------------------------------------------------------------------
+describe_integer() ->
+ {ok,[{"int1",sql_smallint},
+ {"int2",sql_integer},
+ {"int3",sql_integer}]}.
+
+describe_string() ->
+ {ok,[{"str1",{sql_char,10}},
+ {"str2",{sql_char,10}},
+ {"str3",{sql_varchar,10}},
+ {"str4",{sql_varchar,10}}]}.
+
+describe_floating() ->
+ {ok,[{"f",sql_real},{"r",sql_real},{"d",{sql_float,15}}]}.
+describe_dec_num() ->
+ {ok,[{"dec",{sql_numeric,9,3}},{"num",{sql_numeric,9,2}}]}.
+
+describe_timestamp() ->
+ {ok, [{"field", sql_timestamp}]}.
diff --git a/lib/odbc/test/sqlserver.erl b/lib/odbc/test/sqlserver.erl
new file mode 100644
index 0000000000..e3fe30e0bc
--- /dev/null
+++ b/lib/odbc/test/sqlserver.erl
@@ -0,0 +1,298 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(sqlserver).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+%-------------------------------------------------------------------------
+connection_string() ->
+ "DSN=sql-server;UID=odbctest;PWD=gurka".
+
+%-------------------------------------------------------------------------
+insert_result() ->
+ {selected,["ID","DATA"],[{1,"bar"}]}.
+
+update_result() ->
+ {selected,["ID","DATA"],[{1,"foo"}]}.
+
+selected_ID(N, _) ->
+ {selected,["ID"],[{N}]}.
+
+selected_next_N(1)->
+ {selected,["ID"],
+ [{1},
+ {2},
+ {3}]};
+
+selected_next_N(2)->
+ {selected,["ID"],
+ [{4},
+ {5}]}.
+
+selected_relative_N(1)->
+ {selected,["ID"],
+ [{2},
+ {3},
+ {4}]};
+
+selected_relative_N(2)->
+ {selected,["ID"],
+ [{7},
+ {8}]}.
+
+selected_absolute_N(1)->
+ {selected,["ID"],
+ [{1},
+ {2},
+ {3}]};
+
+selected_absolute_N(2)->
+ {selected,["ID"],
+ [{1},
+ {2},
+ {3},
+ {4},
+ {5}]}.
+
+selected_list_rows() ->
+ {selected,["ID", "DATA"],[[1, "bar"],[2,"foo"]]}.
+
+first_list_rows() ->
+ {selected,["ID", "DATA"],[[1, "bar"]]}.
+last_list_rows() ->
+ {selected,["ID", "DATA"],[[2, "foo"]]}.
+prev_list_rows() ->
+ {selected,["ID", "DATA"],[[1, "bar"]]}.
+next_list_rows() ->
+ {selected,["ID", "DATA"],[[2, "foo"]]}.
+
+multiple_select()->
+ [{selected,["ID", "DATA"],[{1, "bar"},{2, "foo"}]},
+ {selected,["DATA"],[{"foo"}]}].
+
+multiple_mix()->
+ [{updated, 1},{updated, 1},
+ {selected,["ID", "DATA"],[{1, "foobar"},{2, "foo"}]},
+ {updated, 1}, {selected,["DATA"],[{"foo"}]}].
+
+%-------------------------------------------------------------------------
+fixed_char_min() ->
+ 1.
+
+fixed_char_max() ->
+ 8000.
+
+create_fixed_char_table(Size) ->
+ " (FIELD char(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+var_char_min() ->
+ 1.
+var_char_max() ->
+ 8000.
+
+create_var_char_table(Size) ->
+ " (FIELD varchar(" ++ integer_to_list(Size) ++ "))".
+%-------------------------------------------------------------------------
+text_min() ->
+ 1.
+text_max() ->
+ 2147483647. %% 2^31 - 1
+
+create_text_table() ->
+ " (FIELD text)".
+
+%-------------------------------------------------------------------------
+create_unicode_table() ->
+ " (FIELD nvarchar(50))".
+
+%-------------------------------------------------------------------------
+create_timestamp_table() ->
+ " (FIELD DATETIME)".
+
+%-------------------------------------------------------------------------
+tiny_int_min() ->
+ 0.
+tiny_int_max() ->
+ 255.
+
+create_tiny_int_table() ->
+ " (FIELD tinyint)".
+
+tiny_int_min_selected() ->
+ {selected,["FIELD"],[{tiny_int_min()}]}.
+
+tiny_int_max_selected() ->
+ {selected,["FIELD"], [{tiny_int_max()}]}.
+
+%-------------------------------------------------------------------------
+small_int_min() ->
+ -32768. % -2^15
+small_int_max() ->
+ 32767. % 2^15-1
+
+create_small_int_table() ->
+ " (FIELD smallint)".
+
+small_int_min_selected() ->
+ {selected,["FIELD"],[{small_int_min()}]}.
+
+small_int_max_selected() ->
+ {selected,["FIELD"], [{small_int_max()}]}.
+
+%-------------------------------------------------------------------------
+int_min() ->
+ -2147483648. % -2^31
+int_max() ->
+ 2147483647. % 2^31-1
+
+create_int_table() ->
+ " (FIELD int)".
+
+int_min_selected() ->
+ {selected,["FIELD"],[{int_min()}]}.
+
+int_max_selected() ->
+ {selected,["FIELD"], [{int_max()}]}.
+
+%-------------------------------------------------------------------------
+big_int_min() ->
+ -9223372036854775808. % -2^63
+big_int_max() ->
+ 9223372036854775807. % 2^63-1
+
+create_big_int_table() ->
+ " (FIELD bigint)".
+
+big_int_min_selected() ->
+ {selected,["FIELD"],[{integer_to_list(big_int_min())}]}.
+
+big_int_max_selected() ->
+ {selected,["FIELD"], [{integer_to_list(big_int_max())}]}.
+
+%-------------------------------------------------------------------------
+bit_false() ->
+ 0.
+bit_true() ->
+ 1.
+
+create_bit_table() ->
+ " (FIELD bit)".
+
+bit_false_selected() ->
+ {selected,["FIELD"],[{false}]}.
+
+bit_true_selected() ->
+ {selected,["FIELD"], [{true}]}.
+%-------------------------------------------------------------------------
+float_min() ->
+ -1.79e+308.
+float_max() ->
+ 1.79e+308.
+
+float_underflow() ->
+ "'-1.80e+308'".
+
+float_overflow() ->
+ "'-1.80e+308'".
+
+create_float_table() ->
+ " (FIELD float)".
+
+float_zero_selected() ->
+ {selected,["FIELD"],[{0.00000e+0}]}.
+%-------------------------------------------------------------------------
+real_min() ->
+ -3.40e+38.
+real_max() ->
+ 3.40e+38.
+
+real_underflow() ->
+ -3.41e+38.
+
+real_overflow() ->
+ 3.41e+38.
+
+create_real_table() ->
+ " (FIELD real)".
+
+real_zero_selected() ->
+ {selected,["FIELD"],[{0.00000e+0}]}.
+%-------------------------------------------------------------------------
+param_select_tiny_int() ->
+ {selected,["FIELD"],[{1}, {2}]}.
+
+param_select_small_int() ->
+ {selected,["FIELD"],[{1}, {2}]}.
+
+param_select_int() ->
+ Int = small_int_max() + 1,
+ {selected,["FIELD"],[{1}, {Int}]}.
+
+param_select_decimal() ->
+ {selected,["FIELD"],[{1},{2}]}.
+
+param_select_numeric() ->
+ {selected,["FIELD"],[{1},{2}]}.
+
+param_select_float() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_real() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_double() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_mix() ->
+ {selected,["ID","DATA"],[{1, "foo"}, {2, "bar"}]}.
+
+param_update() ->
+ {selected,["ID","DATA"],[{1, "foobar"}, {2, "foobar"}, {3, "baz"}]}.
+
+param_delete() ->
+ {selected,["ID","DATA"],[{3, "baz"}]}.
+
+param_select() ->
+ {selected,["ID","DATA"],[{1, "foo"},{3, "foo"}]}.
+
+%-------------------------------------------------------------------------
+
+describe_integer() ->
+ {ok,[{"int1", sql_smallint},{"int2", sql_integer},
+ {"int3", sql_integer}]}.
+
+describe_string() ->
+ {ok,[{"str1",{sql_char,10}},
+ {"str2",{sql_char,10}},
+ {"str3",{sql_varchar,10}},
+ {"str4",{sql_varchar,10}}]}.
+
+describe_floating() ->
+ {ok,[{"f", sql_real},{"r", sql_real}, {"d", {sql_float, 53}}]}.
+
+describe_dec_num() ->
+ {ok,[{"dec",{sql_decimal,9,3}},{"num",{sql_numeric,9,2}}]}.
+
+describe_timestamp() ->
+ {ok, [{"field", sql_timestamp}]}.
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index daf4ef48b7..f425886ce5 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -28,10 +28,11 @@
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
-include("ssl_cipher.hrl").
+-include("ssl_alert.hrl").
-include("ssl_debug.hrl").
-export([security_parameters/2, suite_definition/1,
- decipher/4, cipher/4,
+ decipher/5, cipher/4,
suite/1, suites/1,
openssl_suite/1, openssl_suite_name/1]).
@@ -123,7 +124,7 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
{T, CS0#cipher_state{iv=NextIV}}.
%%--------------------------------------------------------------------
-%% Function: decipher(Method, CipherState, Mac, Data) ->
+%% Function: decipher(Method, CipherState, Mac, Data, Version) ->
%% {Decrypted, UpdateCipherState}
%%
%% Method - integer() (as defined in ssl_cipher.hrl)
@@ -133,9 +134,9 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
%% Description: Decrypts the data and the mac using method, updating
%% the cipher state
%%-------------------------------------------------------------------
-decipher(?NULL, _HashSz, CipherState, Fragment) ->
+decipher(?NULL, _HashSz, CipherState, Fragment, _) ->
{Fragment, <<>>, CipherState};
-decipher(?RC4, HashSz, CipherState, Fragment) ->
+decipher(?RC4, HashSz, CipherState, Fragment, _) ->
?DBG_TERM(CipherState#cipher_state.key),
State0 = case CipherState#cipher_state.state of
undefined -> crypto:rc4_set_key(CipherState#cipher_state.key);
@@ -148,43 +149,47 @@ decipher(?RC4, HashSz, CipherState, Fragment) ->
GSC = generic_stream_cipher_from_bin(T, HashSz),
#generic_stream_cipher{content=Content, mac=Mac} = GSC,
{Content, Mac, CipherState#cipher_state{state=State1}};
-decipher(?DES, HashSz, CipherState, Fragment) ->
+decipher(?DES, HashSz, CipherState, Fragment, Version) ->
block_decipher(fun(Key, IV, T) ->
crypto:des_cbc_decrypt(Key, IV, T)
- end, CipherState, HashSz, Fragment);
-%% decipher(?DES40, HashSz, CipherState, Fragment) ->
+ end, CipherState, HashSz, Fragment, Version);
+%% decipher(?DES40, HashSz, CipherState, Fragment, Version) ->
%% block_decipher(fun(Key, IV, T) ->
%% crypto:des_cbc_decrypt(Key, IV, T)
-%% end, CipherState, HashSz, Fragment);
-decipher(?'3DES', HashSz, CipherState, Fragment) ->
+%% end, CipherState, HashSz, Fragment, Version);
+decipher(?'3DES', HashSz, CipherState, Fragment, Version) ->
block_decipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) ->
crypto:des3_cbc_decrypt(K1, K2, K3, IV, T)
- end, CipherState, HashSz, Fragment);
-decipher(?AES, HashSz, CipherState, Fragment) ->
+ end, CipherState, HashSz, Fragment, Version);
+decipher(?AES, HashSz, CipherState, Fragment, Version) ->
block_decipher(fun(Key, IV, T) when byte_size(Key) =:= 16 ->
crypto:aes_cbc_128_decrypt(Key, IV, T);
(Key, IV, T) when byte_size(Key) =:= 32 ->
crypto:aes_cbc_256_decrypt(Key, IV, T)
- end, CipherState, HashSz, Fragment).
-%% decipher(?IDEA, HashSz, CipherState, Fragment) ->
+ end, CipherState, HashSz, Fragment, Version).
+%% decipher(?IDEA, HashSz, CipherState, Fragment, Version) ->
%% block_decipher(fun(Key, IV, T) ->
%% crypto:idea_cbc_decrypt(Key, IV, T)
-%% end, CipherState, HashSz, Fragment);
+%% end, CipherState, HashSz, Fragment, Version);
block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
- HashSz, Fragment) ->
+ HashSz, Fragment, Version) ->
?DBG_HEX(Key),
?DBG_HEX(IV),
?DBG_HEX(Fragment),
T = Fun(Key, IV, Fragment),
?DBG_HEX(T),
GBC = generic_block_cipher_from_bin(T, HashSz),
- ok = check_padding(GBC), %% TODO kolla ocks�...
- Content = GBC#generic_block_cipher.content,
- Mac = GBC#generic_block_cipher.mac,
- CipherState1 = CipherState0#cipher_state{iv=next_iv(Fragment, IV)},
- {Content, Mac, CipherState1}.
-
+ case is_correct_padding(GBC, Version) of
+ true ->
+ Content = GBC#generic_block_cipher.content,
+ Mac = GBC#generic_block_cipher.mac,
+ CipherState1 = CipherState0#cipher_state{iv=next_iv(Fragment, IV)},
+ {Content, Mac, CipherState1};
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end.
+
%%--------------------------------------------------------------------
%% Function: suites(Version) -> [Suite]
%%
@@ -540,9 +545,12 @@ generic_stream_cipher_from_bin(T, HashSz) ->
#generic_stream_cipher{content=Content,
mac=Mac}.
-check_padding(_GBC) ->
- ok.
+is_correct_padding(_, {3, 0}) ->
+ true;
+is_correct_padding(#generic_block_cipher{padding_length = Len, padding = Padding}, _) ->
+ list_to_binary(lists:duplicate(Len, Len)) == Padding.
+
get_padding(Length, BlockSize) ->
get_padding_aux(BlockSize, Length rem BlockSize).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 2e853c7cc8..644c2772b2 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -1137,6 +1137,8 @@ sync_send_all_state_event(FsmPid, Event, Timeout) ->
exit:{timeout, _} ->
{error, timeout};
exit:{normal, _} ->
+ {error, closed};
+ exit:{shutdown, _} ->
{error, closed}
end.
@@ -1284,7 +1286,6 @@ server_hello(ServerHello, #state{transport_cb = Transport,
tls_handshake_hashes = Hashes0} = State) ->
CipherSuite = ServerHello#server_hello.cipher_suite,
{KeyAlgorithm, _, _} = ssl_cipher:suite_definition(CipherSuite),
- %% Version = ServerHello#server_hello.server_version, TODO ska kontrolleras
{BinMsg, ConnectionStates1, Hashes1} =
encode_handshake(ServerHello, Version, ConnectionStates0, Hashes0),
Transport:send(Socket, BinMsg),
@@ -1726,6 +1727,9 @@ opposite_role(server) ->
send_user(Pid, Msg) ->
Pid ! Msg.
+next_state(_, #alert{} = Alert, #state{negotiated_version = Version} = State) ->
+ handle_own_alert(Alert, Version, decipher_error, State),
+ {stop, normal, State};
next_state(Next, no_record, State) ->
{next_state, Next, State};
@@ -1803,8 +1807,12 @@ next_record(#state{tls_cipher_texts = [], socket = Socket} = State) ->
{no_record, State};
next_record(#state{tls_cipher_texts = [CT | Rest],
connection_states = ConnStates0} = State) ->
- {Plain, ConnStates} = ssl_record:decode_cipher_text(CT, ConnStates0),
- {Plain, State#state{tls_cipher_texts = Rest, connection_states = ConnStates}}.
+ case ssl_record:decode_cipher_text(CT, ConnStates0) of
+ {Plain, ConnStates} ->
+ {Plain, State#state{tls_cipher_texts = Rest, connection_states = ConnStates}};
+ #alert{} = Alert ->
+ {Alert, State}
+ end.
next_record_if_active(State =
#state{socket_options =
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 5f3dff8894..454d726f0d 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -128,16 +128,21 @@ hello(#server_hello{cipher_suite = CipherSuite, server_version = Version,
session_id = SessionId, renegotiation_info = Info},
#ssl_options{secure_renegotiate = SecureRenegotation},
ConnectionStates0, Renegotiation) ->
-
- case handle_renegotiation_info(client, Info, ConnectionStates0,
- Renegotiation, SecureRenegotation, []) of
- {ok, ConnectionStates1} ->
- ConnectionStates =
- hello_pending_connection_states(client, CipherSuite, Random,
- Compression, ConnectionStates1),
- {Version, SessionId, ConnectionStates};
- #alert{} = Alert ->
- Alert
+
+ case ssl_record:is_acceptable_version(Version) of
+ true ->
+ case handle_renegotiation_info(client, Info, ConnectionStates0,
+ Renegotiation, SecureRenegotation, []) of
+ {ok, ConnectionStates1} ->
+ ConnectionStates =
+ hello_pending_connection_states(client, CipherSuite, Random,
+ Compression, ConnectionStates1),
+ {Version, SessionId, ConnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
end;
hello(#client_hello{client_version = ClientVersion, random = Random,
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index 43f18d95a0..7c4b0ee959 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -517,13 +517,17 @@ decode_cipher_text(CipherText, ConnnectionStates0) ->
#connection_state{compression_state = CompressionS0,
security_parameters = SecParams} = ReadState0,
CompressAlg = SecParams#security_parameters.compression_algorithm,
- {Compressed, ReadState1} = decipher(CipherText, ReadState0),
- {Plain, CompressionS1} = uncompress(CompressAlg,
- Compressed, CompressionS0),
- ConnnectionStates = ConnnectionStates0#connection_states{
- current_read = ReadState1#connection_state{
- compression_state = CompressionS1}},
- {Plain, ConnnectionStates}.
+ case decipher(CipherText, ReadState0) of
+ {Compressed, ReadState1} ->
+ {Plain, CompressionS1} = uncompress(CompressAlg,
+ Compressed, CompressionS0),
+ ConnnectionStates = ConnnectionStates0#connection_states{
+ current_read = ReadState1#connection_state{
+ compression_state = CompressionS1}},
+ {Plain, ConnnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -647,29 +651,37 @@ encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) ->
cipher(Type, Version, Fragment, CS0) ->
Length = erlang:iolist_size(Fragment),
- {Hash, CS1=#connection_state{cipher_state = CipherS0,
+ {MacHash, CS1=#connection_state{cipher_state = CipherS0,
security_parameters=
#security_parameters{bulk_cipher_algorithm =
BCA}
}} =
hash_and_bump_seqno(CS0, Type, Version, Length, Fragment),
?DBG_HEX(Fragment),
- {Ciphered, CipherS1} = ssl_cipher:cipher(BCA, CipherS0, Hash, Fragment),
+ {Ciphered, CipherS1} = ssl_cipher:cipher(BCA, CipherS0, MacHash, Fragment),
?DBG_HEX(Ciphered),
CS2 = CS1#connection_state{cipher_state=CipherS1},
{Ciphered, CS2}.
decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) ->
SP = CS0#connection_state.security_parameters,
- BCA = SP#security_parameters.bulk_cipher_algorithm, % eller Cipher?
+ BCA = SP#security_parameters.bulk_cipher_algorithm,
HashSz = SP#security_parameters.hash_size,
CipherS0 = CS0#connection_state.cipher_state,
- {T, Mac, CipherS1} = ssl_cipher:decipher(BCA, HashSz, CipherS0, Fragment),
- CS1 = CS0#connection_state{cipher_state = CipherS1},
- TLength = size(T),
- {Hash, CS2} = hash_and_bump_seqno(CS1, Type, Version, TLength, Fragment),
- ok = check_hash(Hash, Mac),
- {TLS#ssl_tls{fragment = T}, CS2}.
+ case ssl_cipher:decipher(BCA, HashSz, CipherS0, Fragment, Version) of
+ {T, Mac, CipherS1} ->
+ CS1 = CS0#connection_state{cipher_state = CipherS1},
+ TLength = size(T),
+ {MacHash, CS2} = hash_and_bump_seqno(CS1, Type, Version, TLength, T),
+ case is_correct_mac(Mac, MacHash) of
+ true ->
+ {TLS#ssl_tls{fragment = T}, CS2};
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+ #alert{} = Alert ->
+ Alert
+ end.
uncompress(?NULL, Data = #ssl_tls{type = _Type,
version = _Version,
@@ -690,8 +702,11 @@ hash_and_bump_seqno(#connection_state{sequence_number = SeqNo,
Length, Fragment),
{Hash, CS0#connection_state{sequence_number = SeqNo+1}}.
-check_hash(_, _) ->
- ok. %% TODO check this
+is_correct_mac(Mac, Mac) ->
+ true;
+is_correct_mac(_M,_H) ->
+ io:format("Mac ~p ~n Hash: ~p~n",[_M, _H]),
+ false.
mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type,
_Length, _Fragment) ->
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index dc110e2940..522eda54d6 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -2129,9 +2129,7 @@ server_require_peer_cert_fail(Config) when is_list(Config) ->
{options, [{active, false} | BadClientOpts]}]),
ssl_test_lib:check_result(Server, {error, esslaccept},
- Client, {error, esslconnect}),
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client).
+ Client, {error, esslconnect}).
%%--------------------------------------------------------------------
@@ -2648,11 +2646,8 @@ invalid_signature_server(Config) when is_list(Config) ->
{options, [{verify, verify_peer} | ClientOpts]}]),
ssl_test_lib:check_result(Server, {error, "bad certificate"},
- Client, {error,"bad certificate"}),
+ Client, {error,"bad certificate"}).
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client).
-
%%--------------------------------------------------------------------
invalid_signature_client(doc) ->
@@ -2691,10 +2686,7 @@ invalid_signature_client(Config) when is_list(Config) ->
{options, NewClientOpts}]),
ssl_test_lib:check_result(Server, {error, "bad certificate"},
- Client, {error,"bad certificate"}),
-
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client).
+ Client, {error,"bad certificate"}).
%%--------------------------------------------------------------------
cert_expired(doc) ->
@@ -2754,11 +2746,7 @@ cert_expired(Config) when is_list(Config) ->
{options, [{verify, verify_peer} | ClientOpts]}]),
ssl_test_lib:check_result(Server, {error, "certificate expired"},
- Client, {error, "certificate expired"}),
-
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client).
-
+ Client, {error, "certificate expired"}).
two_digits_str(N) when N < 10 ->
lists:flatten(io_lib:format("0~p", [N]));
diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl
index d0dbf4a2b4..1656899e8f 100644
--- a/lib/tools/src/xref_base.erl
+++ b/lib/tools/src/xref_base.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -29,7 +29,7 @@
add_release/2, add_release/3,
get_library_path/1, set_library_path/2, set_library_path/3,
set_up/1, set_up/2,
- q/2, q/3, info/1, info/2, info/3, update/1, update/2,
+ q/2, q/3, info/1, info/2, info/3, update/1, update/2,
forget/1, forget/2, variables/1, variables/2,
analyze/2, analyze/3, analysis/1,
get_default/2, set_default/3,
@@ -38,14 +38,14 @@
-export([format_error/1]).
%% The following functions are exported for testing purposes only:
--export([do_add_module/4, do_add_application/2, do_add_release/2,
+-export([do_add_module/4, do_add_application/2, do_add_release/2,
do_remove_module/2]).
--import(lists,
- [filter/2, flatten/1, foldl/3, keysearch/3, map/2, mapfoldl/3,
- member/2, reverse/1, sort/1, usort/1]).
+-import(lists,
+ [filter/2, flatten/1, foldl/3, foreach/2, keysearch/3, map/2,
+ mapfoldl/3, member/2, reverse/1, sort/1, usort/1]).
--import(sofs,
+-import(sofs,
[constant_function/2, converse/1, difference/2, domain/1,
empty_set/0, family/1, family_difference/2, intersection/2,
family_projection/2, family_to_relation/1, family_union/1,
@@ -103,12 +103,12 @@ delete(State) ->
ok
end
end,
- map(Fun, dict:to_list(State#xref.variables)),
+ foreach(Fun, dict:to_list(State#xref.variables)),
ok.
add_directory(State, Dir) ->
add_directory(State, Dir, []).
-
+
%% -> {ok, Modules, NewState} | Error
add_directory(State, Dir, Options) ->
ValOptions = option_values([builtins, recurse, verbose, warnings], State),
@@ -277,7 +277,7 @@ q(S, Q, Options) when is_atom(Q) ->
q(S, atom_to_list(Q), Options);
q(S, Q, Options) ->
case xref_utils:is_string(Q, 1) of
- true ->
+ true ->
case set_up(S, Options) of
{ok, S1} ->
case xref_compiler:compile(Q, S1#xref.variables) of
@@ -336,7 +336,7 @@ forget(State, Variable) when is_atom(Variable) ->
forget(State, Variables) ->
Vars = State#xref.variables,
do_forget(Variables, Vars, Variables, State).
-
+
variables(State) ->
variables(State, [user]).
@@ -350,9 +350,9 @@ variables(State, Options) ->
{ok, NewState} ->
{U, P} = do_variables(NewState),
R1 = if User -> [{user, U}]; true -> [] end,
- R = if
- Predef -> [{predefined,P} | R1];
- true -> R1
+ R = if
+ Predef -> [{predefined,P} | R1];
+ true -> R1
end,
{{ok, R}, NewState};
Error ->
@@ -368,7 +368,7 @@ analyze(State, Analysis) ->
%% -> {{ok, Answer}, NewState} | {Error, NewState}
analyze(State, Analysis, Options) ->
case analysis(Analysis, State#xref.mode) of
- P when is_list(P) ->
+ P when is_list(P) ->
q(State, P, Options);
error ->
R = case analysis(Analysis, functions) of
@@ -461,7 +461,7 @@ get_default(State, Option) ->
%% -> [{Option, Value}]
get_default(State) ->
- Fun = fun(O) -> V = current_default(State, O), {O, V} end,
+ Fun = fun(O) -> V = current_default(State, O), {O, V} end,
map(Fun, [builtins, recurse, verbose, warnings]).
%% -> {ok, NewState} -> Error
@@ -478,7 +478,7 @@ set_default(State, Options) ->
format_error({error, Module, Error}) ->
Module:format_error(Error);
format_error({invalid_options, Options}) ->
- io_lib:format("Unknown option(s) or invalid option value(s): ~p~n",
+ io_lib:format("Unknown option(s) or invalid option value(s): ~p~n",
[Options]);
format_error({invalid_filename, Term}) ->
io_lib:format("A file name (a string) was expected: ~p~n", [Term]);
@@ -540,7 +540,7 @@ updated_modules(State) ->
case xref_utils:file_info(File) of
{ok, {_, file, readable, MTime}} when MTime =/= RTime ->
[{M,File} | L];
- _Else ->
+ _Else ->
L
end
end,
@@ -591,7 +591,7 @@ do_add_release(Dir, RelName, OB, OV, OW, State) ->
case xref_utils:release_directory(Dir, true, "ebin") of
{ok, ReleaseDirName, ApplDir, Dirs} ->
ApplDirs = xref_utils:select_last_application_version(Dirs),
- Release = case RelName of
+ Release = case RelName of
[[]] -> ReleaseDirName;
[Name] -> Name
end,
@@ -615,7 +615,7 @@ do_add_release(S, XRel) ->
end.
add_rel_appls([ApplDir | ApplDirs], Release, OB, OV, OW, State) ->
- {ok, _AppName, NewState} =
+ {ok, _AppName, NewState} =
add_appldir(ApplDir, Release, [[]], OB, OV, OW, State),
add_rel_appls(ApplDirs, Release, OB, OV, OW, NewState);
add_rel_appls([], [Release], _OB, _OV, _OW, NewState) ->
@@ -637,10 +637,10 @@ add_appldir(ApplDir, Release, Name, OB, OV, OW, OldState) ->
[[]] -> AppName0;
[N] -> N
end,
- AppInfo = #xref_app{name = AppName, rel_name = Release,
+ AppInfo = #xref_app{name = AppName, rel_name = Release,
vsn = Vsn, dir = Dir},
State1 = do_add_application(OldState, AppInfo),
- {ok, _Modules, NewState} =
+ {ok, _Modules, NewState} =
do_add_directory(Dir, [AppName], OB, false, OV, OW, State1),
{ok, AppName, NewState}.
@@ -662,7 +662,7 @@ do_add_directory(Dir, AppName, Bui, Rec, Ver, War, State) ->
ok = is_filename(Dir),
{FileNames, Errors, Jams, Unreadable} =
xref_utils:scan_directory(Dir, Rec, [?Suffix], [".jam"]),
- warnings(War, jam, Jams),
+ warnings(War, jam, Jams),
warnings(War, unreadable, Unreadable),
case Errors of
[] ->
@@ -683,7 +683,7 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
false ->
throw_error({invalid_filename, File});
Splitname ->
- do_add_module(Splitname, AppName, Builtins, Verbose,
+ do_add_module(Splitname, AppName, Builtins, Verbose,
Warnings, State)
end.
@@ -691,7 +691,7 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
%% Options: verbose, warnings, builtins
do_add_module({Dir, Basename}, AppName, Builtins, Verbose, Warnings, State) ->
File = filename:join(Dir, Basename),
- {ok, M, Bad, NewState} =
+ {ok, M, Bad, NewState} =
do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State),
filter(fun({Tag,B}) -> warnings(Warnings, Tag, [[File,B]]) end, Bad),
{ok, M, NewState}.
@@ -723,7 +723,7 @@ do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State) ->
{ok, {_, _, _, Time}} -> Time;
Error -> throw(Error)
end,
- XMod = #xref_mod{name = M, app_name = AppName, dir = Dir,
+ XMod = #xref_mod{name = M, app_name = AppName, dir = Dir,
mtime = T, builtins = Builtins,
no_unresolved = NoUnresCalls},
do_add_module(State, XMod, UnresCalls, Data);
@@ -736,13 +736,13 @@ abst(File, Builtins, Mode) when Mode =:= functions ->
case beam_lib:chunks(File, [abstract_code, exports, attributes]) of
{ok, {M,[{abstract_code,NoA},_X,_A]}} when NoA =:= no_abstract_code ->
{ok, M, NoA};
- {ok, {M, [{abstract_code, {abstract_v1, Forms}},
+ {ok, {M, [{abstract_code, {abstract_v1, Forms}},
{exports,X0}, {attributes,A}]}} ->
%% R7.
X = xref_utils:fa_to_mfa(X0, M),
D = deprecated(A, X, M),
xref_reader:module(M, Forms, Builtins, X, D);
- {ok, {M, [{abstract_code, {abstract_v2, Forms}},
+ {ok, {M, [{abstract_code, {abstract_v2, Forms}},
{exports,X0}, {attributes,A}]}} ->
%% R8-R9B.
X = xref_utils:fa_to_mfa(X0, M),
@@ -769,8 +769,8 @@ abst(File, Builtins, Mode) when Mode =:= modules ->
true ->
I0;
false ->
- Fun = fun({M,F,A}) ->
- not xref_utils:is_builtin(M, F, A)
+ Fun = fun({M,F,A}) ->
+ not xref_utils:is_builtin(M, F, A)
end,
filter(Fun, I0)
end,
@@ -790,7 +790,7 @@ mfa_exports(X0, Attributes, M) ->
xref_utils:fa_to_mfa(X1, M).
adjust_arity(F, A) ->
- case xref_utils:is_static_function(F, A) of
+ case xref_utils:is_static_function(F, A) of
true -> A;
false -> A - 1
end.
@@ -885,7 +885,7 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
Unres = domain(UnresCalls),
DefinedFuns = domain(DefAt),
- {AXC, ALC, Bad1, LPreCAt2, XPreCAt2} =
+ {AXC, ALC, Bad1, LPreCAt2, XPreCAt2} =
extra_edges(AXC1, ALC1, Bad0, DefinedFuns),
Bad = map(fun(B) -> {xref_attr, B} end, Bad1),
LPreCAt = union(LPreCAt1, LPreCAt2),
@@ -904,8 +904,8 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
%% {EE, ECallAt} = inter_graph(X, L, LC, XC, LCallAt, XCallAt),
Self = self(),
- Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end,
- {EE, ECallAt} =
+ Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end,
+ {EE, ECallAt} =
xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]),
[DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
@@ -977,13 +977,13 @@ extra_edges(CAX, CAL, Bad0, F) ->
ALC = restriction(2, restriction(ALC0, F), F),
LPreCAt2 = restriction(CAL, ALC),
XPreCAt2 = restriction(CAX, AXC),
- Bad = Bad0 ++ to_external(difference(AXC0, AXC))
+ Bad = Bad0 ++ to_external(difference(AXC0, AXC))
++ to_external(difference(ALC0, ALC)),
{AXC, ALC, Bad, LPreCAt2, XPreCAt2}.
no_info(X, L, LC, XC, EE, Unres, NoCalls, NoUnresCalls) ->
NoUnres = no_elements(Unres),
- [{no_calls, {NoCalls-NoUnresCalls, NoUnresCalls}},
+ [{no_calls, {NoCalls-NoUnresCalls, NoUnresCalls}},
{no_function_calls, {no_elements(LC), no_elements(XC)-NoUnres, NoUnres}},
{no_functions, {no_elements(L), no_elements(X)}},
%% Note: this is overwritten in do_set_up():
@@ -1011,10 +1011,10 @@ inter_graph(X, L, LC, XC, CallAt) ->
Es = union(LEs, XEs),
E1 = to_external(restriction(difference(LC, LEs), XL)),
- R0 = xref_utils:xset(reachable(E1, G, []),
+ R0 = xref_utils:xset(reachable(E1, G, []),
[{tspec(func), tspec(fun_edge)}]),
true = digraph:delete(G),
-
+
% RL is a set of indirect local calls to exports.
RL = restriction(R0, XL),
% RX is a set of indirect external calls to exports.
@@ -1033,7 +1033,7 @@ inter_graph(X, L, LC, XC, CallAt) ->
?FORMAT("XL=~p~nXEs=~p~nLEs=~p~nE1=~p~nR0=~p~nRL=~p~nRX=~p~nR=~p~n"
"EE=~p~nECallAt1=~p~nECallAt2=~p~nECallAt=~p~n~n",
- [XL, XEs, LEs, E1, R0, RL, RX, R, EE,
+ [XL, XEs, LEs, E1, R0, RL, RX, R, EE,
ECallAt1, ECallAt2, ECallAt]),
{EE, ECallAt}.
@@ -1121,7 +1121,7 @@ remove_erase([], D) ->
do_add_libraries(Path, Verbose, State) ->
message(Verbose, lib_search, []),
- {C, E} = xref_utils:list_path(Path, [?Suffix]),
+ {C, E} = xref_utils:list_path(Path, [?Suffix]),
message(Verbose, done, []),
MDs = to_external(relation_to_family(relation(C))),
%% message(Verbose, lib_check, []),
@@ -1160,23 +1160,23 @@ do_set_up(S, VerboseOpt) ->
Reply.
%% If data has been supplied using add_module/9 (and that is the only
-%% sanctioned way), then DefAt, L, X, LCallAt, XCallAt, CallAt, XC, LC,
-%% and LU are guaranteed to be functions (with all supplied
-%% modules as domain (disregarding unknown modules, that is, modules
+%% sanctioned way), then DefAt, L, X, LCallAt, XCallAt, CallAt, XC, LC,
+%% and LU are guaranteed to be functions (with all supplied
+%% modules as domain (disregarding unknown modules, that is, modules
%% not supplied but hosting unknown functions)).
%% As a consequence, V and E are also functions. V is defined for unknown
%% modules also.
%% UU is also a function (thanks to sofs:family_difference/2...).
-%% XU on the other hand can be a partial function (that is, not defined
+%% XU on the other hand can be a partial function (that is, not defined
%% for all modules). U is derived from XU, so U is also partial.
%% The inverse variables - LC_1, XC_1, E_1 and EE_1 - are all partial.
%% B is also partial.
do_set_up(S) when S#xref.mode =:= functions ->
ModDictList = dict:to_list(S#xref.modules),
- [DefAt0, L, X0, LCallAt, XCallAt, CallAt, LC, XC, LU,
+ [DefAt0, L, X0, LCallAt, XCallAt, CallAt, LC, XC, LU,
EE0, ECallAt, UC, LPredefined,
Mod_DF,Mod_DF_1,Mod_DF_2,Mod_DF_3] = make_families(ModDictList, 18),
-
+
{XC_1, XU, XPredefined} = do_set_up_1(XC),
LC_1 = user_family(union_of_family(LC)),
E_1 = family_union(XC_1, LC_1),
@@ -1206,7 +1206,7 @@ do_set_up(S) when S#xref.mode =:= functions ->
AM = domain(F1),
%% Undef is the union of U0 and Lib:
- {Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
+ {Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
make_libs(XU, F1, AM, S#xref.library_path, S#xref.libraries),
{B, U} = make_builtins(U0),
X1_B = family_union(X1, B),
@@ -1228,22 +1228,22 @@ do_set_up(S) when S#xref.mode =:= functions ->
%% way to discard calls to local functions in other modules.
EE_conv = converse(union_of_family(EE0)),
EE_exported = restriction(EE_conv, union_of_family(X)),
- EE_local =
+ EE_local =
specification({external, fun({{M1,_,_},{M2,_,_}}) -> M1 =:= M2 end},
EE_conv),
EE_0 = converse(union(EE_local, EE_exported)),
EE_1 = user_family(EE_0),
- EE1 = partition_family({external, fun({{M1,_,_}, _MFA2}) -> M1 end},
+ EE1 = partition_family({external, fun({{M1,_,_}, _MFA2}) -> M1 end},
EE_0),
%% Make sure EE is defined for all modules:
EE = family_union(family_difference(EE0, EE0), EE1),
- IFun =
- fun({Mod,EE_M}, XMods) ->
- IMFun =
+ IFun =
+ fun({Mod,EE_M}, XMods) ->
+ IMFun =
fun(XrefMod) ->
- [NoCalls, NoFunctionCalls,
+ [NoCalls, NoFunctionCalls,
NoFunctions, _NoInter] = XrefMod#xref_mod.info,
- NewInfo = [NoCalls, NoFunctionCalls, NoFunctions,
+ NewInfo = [NoCalls, NoFunctionCalls, NoFunctions,
{no_inter_function_calls,length(EE_M)}],
XrefMod#xref_mod{info = NewInfo}
end,
@@ -1274,11 +1274,11 @@ do_set_up(S) when S#xref.mode =:= functions ->
finish_set_up(S1, Vs);
do_set_up(S) when S#xref.mode =:= modules ->
ModDictList = dict:to_list(S#xref.modules),
- [X0, I0, Mod_DF, Mod_DF_1, Mod_DF_2, Mod_DF_3] =
+ [X0, I0, Mod_DF, Mod_DF_1, Mod_DF_2, Mod_DF_3] =
make_families(ModDictList, 7),
I = union_of_family(I0),
AM = domain(X0),
-
+
{XU, Predefined} = make_predefined(I, AM),
%% Add "hidden" functions to the exports.
X1 = family_union(X0, Predefined),
@@ -1288,8 +1288,8 @@ do_set_up(S) when S#xref.mode =:= modules ->
M2A = make_M2A(ModDictList),
{A2R,A} = make_A2R(S#xref.applications),
R = set(dict:fetch_keys(S#xref.releases)),
-
- ME = projection({external, fun({M1,{M2,_F2,_A2}}) -> {M1,M2} end},
+
+ ME = projection({external, fun({M1,{M2,_F2,_A2}}) -> {M1,M2} end},
family_to_relation(I0)),
ME2AE = multiple_relative_product({M2A, M2A}, ME),
@@ -1298,7 +1298,7 @@ do_set_up(S) when S#xref.mode =:= modules ->
RE = range(AE2RE),
%% Undef is the union of U0 and Lib:
- {_Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
+ {_Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
make_libs(XU, X1, AM, S#xref.library_path, S#xref.libraries),
{B, U} = make_builtins(U0),
X1_B = family_union(X1, B),
@@ -1312,7 +1312,7 @@ do_set_up(S) when S#xref.mode =:= modules ->
X = family_union(X1, Lib),
Empty = empty_set(),
- Vs = [{'X',X},{'U',U},{'B',B},{'XU',XU},{v,V},
+ Vs = [{'X',X},{'U',U},{'B',B},{'XU',XU},{v,V},
{e,{Empty,Empty}},
{'M',M},{'A',A},{'R',R},
{'AM',AM},{'UM',UM},{'LM',LM},
@@ -1328,10 +1328,10 @@ finish_set_up(S, Vs) ->
S1 = S#xref{variables = T},
%% io:format("~p <= state <= ~p~n", [pack:lsize(S), pack:usize(S)]),
{ok, S1}.
-
+
do_finish_set_up([{Key, Value} | Vs], T) ->
{Type, OType} = var_type(Key),
- Val = #xref_var{name = Key, value = Value, vtype = predef,
+ Val = #xref_var{name = Key, value = Value, vtype = predef,
otype = OType, type = Type},
T1 = dict:store(Key, Val, T),
do_finish_set_up(Vs, T1);
@@ -1362,15 +1362,15 @@ var_type('EE') -> {function, edge};
var_type('LC') -> {function, edge};
var_type('UC') -> {function, edge};
var_type('XC') -> {function, edge};
-var_type('AE') -> {application, edge};
-var_type('ME') -> {module, edge};
+var_type('AE') -> {application, edge};
+var_type('ME') -> {module, edge};
var_type('RE') -> {release, edge};
var_type(_) -> {foo, bar}.
make_families(ModDictList, N) ->
Fun1 = fun({_,XMod}) -> XMod#xref_mod.data end,
Ss = from_sets(map(Fun1, ModDictList)),
- %% io:format("~n~p <= module data <= ~p~n",
+ %% io:format("~n~p <= module data <= ~p~n",
%% [pack:lsize(Ss), pack:usize(Ss)]),
make_fams(N, Ss, []).
@@ -1389,7 +1389,7 @@ make_M2A(ModDictList) ->
make_A2R(ApplDict) ->
AppDict = dict:to_list(ApplDict),
Fun = fun({A,XApp}) -> {A, XApp#xref_app.rel_name} end,
- Appl0 = family(map(Fun, AppDict)),
+ Appl0 = family(map(Fun, AppDict)),
AllApps = domain(Appl0),
Appl = family_to_relation(Appl0),
{Appl, AllApps}.
@@ -1445,13 +1445,13 @@ make_libs(XU, F, AM, LibPath, LibDict) ->
false ->
Libraries = dict:to_list(LibDict),
Lb = restriction(a_function(Libraries), UM),
- MFun = fun({M,XLib}) ->
+ MFun = fun({M,XLib}) ->
#xref_lib{dir = Dir} = XLib,
xref_utils:module_filename(Dir, M)
end,
map(MFun, to_external(Lb))
end,
- Fun = fun(FileName, Deprs) ->
+ Fun = fun(FileName, Deprs) ->
case beam_lib:chunks(FileName, [exports, attributes]) of
{ok, {M, [{exports,X}, {attributes,A}]}} ->
Exports = mfa_exports(X, A, M),
@@ -1496,14 +1496,14 @@ user_family(R) ->
partition_family({external, fun({_MFA1, {M2,_,_}}) -> M2 end}, R).
do_variables(State) ->
- Fun = fun({Name, #xref_var{vtype = user}}, {P,U}) ->
+ Fun = fun({Name, #xref_var{vtype = user}}, {P,U}) ->
{P,[Name | U]};
- ({Name, #xref_var{vtype = predef}}, A={P,U}) ->
+ ({Name, #xref_var{vtype = predef}}, A={P,U}) ->
case atom_to_list(Name) of
[H|_] when H>= $a, H=<$z -> A;
_Else -> {[Name | P], U}
end;
- ({{tmp, V}, _}, A) ->
+ ({{tmp, V}, _}, A) ->
io:format("Bug in ~p: temporary ~p~n", [?MODULE, V]), A;
(_V, A) -> A
end,
@@ -1565,7 +1565,7 @@ do_info(S, libraries) ->
map(fun({_L,XLib}) -> lib_info(XLib) end, D);
do_info(_S, I) ->
error({no_such_info, I}).
-
+
do_info(S, Type, E) when is_atom(E) ->
do_info(S, Type, [E]);
do_info(S, modules, Modules0) when is_list(Modules0) ->
@@ -1598,7 +1598,7 @@ find_info([E | Es], Dict, Error) ->
{ok, X} ->
[X | find_info(Es, Dict, Error)]
end;
-find_info([], _Dict, _Error) ->
+find_info([], _Dict, _Error) ->
[].
%% -> {[{AppName, RelName}], [{RelName, XApp}]}
@@ -1618,7 +1618,7 @@ rel_apps(S) ->
rel_apps_sums(AR, RRA0, S) ->
AppMods = app_mods(S), % [{AppName, XMod}]
RRA1 = relation_to_family(relation(RRA0)),
- RRA = inverse(substitution(1, RRA1)),
+ RRA = inverse(substitution(1, RRA1)),
%% RRA is [{RelName,{RelName,[XApp]}}]
RelMods = relative_product1(relation(AR), relation(AppMods)),
RelAppsMods = relative_product1(RRA, RelMods),
@@ -1630,7 +1630,7 @@ rel_apps_sums(AR, RRA0, S) ->
%% -> [{AppName, XMod}]
app_mods(S) ->
D = sort(dict:to_list(S#xref.modules)),
- Fun = fun({_M,XMod}, Acc) ->
+ Fun = fun({_M,XMod}, Acc) ->
case XMod#xref_mod.app_name of
[] -> Acc;
[AppName] -> [{AppName, XMod} | Acc]
@@ -1639,7 +1639,7 @@ app_mods(S) ->
foldl(Fun, [], D).
mod_info(XMod) ->
- #xref_mod{name = M, app_name = AppName, builtins = BuiltIns,
+ #xref_mod{name = M, app_name = AppName, builtins = BuiltIns,
dir = Dir, info = Info} = XMod,
App = sup_info(AppName),
{M, [{application, App}, {builtins, BuiltIns}, {directory, Dir} | Info]}.
@@ -1649,7 +1649,7 @@ app_info({AppName, ModSums}, S) ->
#xref_app{rel_name = RelName, vsn = Vsn, dir = Dir} = XApp,
Release = sup_info(RelName),
{AppName, [{directory,Dir}, {release, Release}, {version,Vsn} | ModSums]}.
-
+
rel_info({{RelName, XApps}, ModSums}, S) ->
NoApps = length(XApps),
XRel = dict:fetch(RelName, S#xref.releases),
@@ -1678,16 +1678,16 @@ no_sum(S, L) when S#xref.mode =:= modules ->
[{no_analyzed_modules, length(L)}].
no_sum([XMod | D], C0, UC0, LC0, XC0, UFC0, L0, X0, EV0, NoM) ->
- [{no_calls, {C,UC}},
+ [{no_calls, {C,UC}},
{no_function_calls, {LC,XC,UFC}},
{no_functions, {L,X}},
{no_inter_function_calls, EV}] = XMod#xref_mod.info,
no_sum(D, C0+C, UC0+UC, LC0+LC, XC0+XC, UFC0+UFC, L0+L, X0+X, EV0+EV, NoM);
no_sum([], C, UC, LC, XC, UFC, L, X, EV, NoM) ->
[{no_analyzed_modules, NoM},
- {no_calls, {C,UC}},
+ {no_calls, {C,UC}},
{no_function_calls, {LC,XC,UFC}},
- {no_functions, {L,X}},
+ {no_functions, {L,X}},
{no_inter_function_calls, EV}].
%% -> ok | throw(Error)
@@ -1712,20 +1712,20 @@ warnings(Flag, Message, [F | Fs]) ->
%% pack(term()) -> term()
%%
%% The identify function. The returned term does not use more heap
-%% than the given term. Tuples that are equal (=:=/2) are made
+%% than the given term. Tuples that are equal (=:=/2) are made
%% "the same".
%%
%% The process dictionary is used because it seems to be faster than
%% anything else right now...
%%
%pack(T) -> T;
-pack(T) ->
+pack(T) ->
PD = erase(),
NT = pack1(T),
%% true = T =:= NT,
%% io:format("erasing ~p elements...~n", [length(erase())]),
erase(), % wasting heap (and time)...
- map(fun({K,V}) -> put(K, V) end, PD),
+ foreach(fun({K,V}) -> put(K, V) end, PD),
NT.
pack1(C) when not is_tuple(C), not is_list(C) ->
diff --git a/lib/tools/src/xref_compiler.erl b/lib/tools/src/xref_compiler.erl
index 67ac8c617d..c80eb0e669 100644
--- a/lib/tools/src/xref_compiler.erl
+++ b/lib/tools/src/xref_compiler.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -37,15 +37,15 @@
-export([format_error/1]).
--import(lists,
+-import(lists,
[concat/1, foldl/3, nthtail/2, reverse/1, sort/1, sublist/2]).
-import(sofs,
[composite/2, difference/2, empty_set/0, from_term/1,
intersection/2, is_empty_set/1, multiple_relative_product/2,
projection/2, relation/1, relation_to_family/1,
- restriction/2, substitution/2, to_external/1, union/2,
- union_of_family/1]).
+ restriction/2, specification/2, substitution/2,
+ to_external/1, union/2, union_of_family/1]).
%%
%% Exported functions
@@ -75,7 +75,7 @@ compile(Chars, Table) ->
{error, Info, Line} ->
error({parse_error, Line, Info})
end.
-
+
format_error({error, Module, Error}) ->
Module:format_error(Error);
format_error({parse_error, Line, Error}) ->
@@ -115,7 +115,7 @@ statements([Stmt={assign, VarType, Name, E} | Stmts0], Table, L, UV) ->
throw_error({variable_reassigned, xref_parser:t2s(Stmt)});
error ->
{Type, OType, NewE} = t_expr(E, Table),
- Val = #xref_var{name = Name, vtype = VarType,
+ Val = #xref_var{name = Name, vtype = VarType,
otype = OType, type = Type},
NewTable = dict:store(Name, Val, Table),
Stmts = if Stmts0 =:= [] -> [{variable, Name}]; true -> Stmts0 end,
@@ -128,9 +128,9 @@ statements([Expr], Table, L, UV) ->
E1 = un_familiarize(Type, OType, NewE),
NE = case {Type, OType} of
%% Edges with empty sets of line numbers are removed.
- {{line, _}, edge} ->
+ {{line, _}, edge} ->
{relation_to_family, E1};
- {_Type, edge_closure} ->
+ {_Type, edge_closure} ->
%% Fake a closure usage, just to make sure it is destroyed.
E2 = {fun graph_access/2, E1, E1},
{fun(_E) -> 'closure()' end, E2};
@@ -163,7 +163,7 @@ t_expr(E, Table) ->
%%% Constant = atom() | {atom(), atom()} | MFA | {MFA, MFA}
%%% Call = atom() % function in the sofs module
%%% | fun()
-%%% Type = {line, LineType} | function | module | application | release
+%%% Type = {line, LineType} | function | module | application | release
%%% | number
%%% LineType = line | local_call | external_call | export_call | all_line_call
%%% VarType = predef | user | tmp
@@ -182,7 +182,7 @@ check_expr({variable, Name}, Table) ->
case dict:find(Name, Table) of
{ok, #xref_var{vtype = VarType, otype = OType, type = Type}} ->
V0 = {variable, {VarType, Name}},
- V = case {VarType, Type, OType} of
+ V = case {VarType, Type, OType} of
{predef, release, _} -> V0;
{predef, application, _} -> V0;
{predef, module, _} -> V0;
@@ -212,7 +212,7 @@ check_expr(Expr={set, SOp, E}, Table) ->
{edge_set, domain} -> vertex_set;
{edge_set, weak} -> edge_set;
{edge_set, strict} -> edge_set;
- _ ->
+ _ ->
throw_error({type_error, xref_parser:t2s(Expr)})
end,
Op = set_op(SOp),
@@ -223,10 +223,10 @@ check_expr(Expr={graph, Op, E}, Table) ->
case Type of
{line, _LineType} ->
throw_error({type_error, xref_parser:t2s(Expr)});
- _Else ->
+ _Else ->
ok
end,
- OType =
+ OType =
case {NOType, Op} of
{edge, components} -> vertex_set;
{edge, condensation} -> edge_set;
@@ -237,7 +237,7 @@ check_expr(Expr={graph, Op, E}, Table) ->
%% Neither need nor want these ones:
%% {edge_set, closure} -> edge_set_closure;
%% {edge_set, components} -> vertex_set_set;
- _ ->
+ _ ->
throw_error({type_error, xref_parser:t2s(Expr)})
end,
E2 = {convert, NOType, edge_closure, E1},
@@ -271,10 +271,10 @@ check_expr(Expr={set, SOp, E1, E2}, Table) ->
number ->
{expr, number, number, {call, ari_op(SOp), NE1, NE2}};
_Else -> % set
- {Type, NewE1, NewE2} =
+ {Type, NewE1, NewE2} =
case {type_ord(Type1), type_ord(Type2)} of
{T1, T2} when T1 =:= T2 ->
- %% Example: if Type1 = {line, line} and
+ %% Example: if Type1 = {line, line} and
%% Type2 = {line, export_line}, then this is not
%% correct, but works:
{Type1, NE1, NE2};
@@ -296,7 +296,7 @@ check_expr(Expr={restr, ROp, E1, E2}, Table) ->
throw_error({type_error, xref_parser:t2s(Expr)});
{_Type1, {line, _LineType2}} ->
throw_error({type_error, xref_parser:t2s(Expr)});
- _ ->
+ _ ->
ok
end,
case {OType1, OType2} of
@@ -307,14 +307,14 @@ check_expr(Expr={restr, ROp, E1, E2}, Table) ->
{edge, vertex} ->
restriction(ROp, E1, Type1, NE1, Type2, NE2);
{edge_closure, vertex} when ROp =:= '|||' ->
- {expr, _, _, R1} =
+ {expr, _, _, R1} =
closure_restriction('|', Type1, Type2, OType2, NE1, NE2),
- {expr, _, _, R2} =
+ {expr, _, _, R2} =
closure_restriction('||', Type1, Type2, OType2, NE1, NE2),
{expr, Type1, edge, {call, intersection, R1, R2}};
- {edge_closure, vertex} ->
+ {edge_closure, vertex} ->
closure_restriction(ROp, Type1, Type2, OType2, NE1, NE2);
- _ ->
+ _ ->
throw_error({type_error, xref_parser:t2s(Expr)})
end;
check_expr(Expr={path, E1, E2}, Table) ->
@@ -330,7 +330,7 @@ check_expr(Expr={path, E1, E2}, Table) ->
end,
E2b = {convert, OType2, Type2, Type1, E2a},
{OType1, NE1} = path_arg(OType1a, E1a),
- NE2 = case {OType1, OType2} of
+ NE2 = case {OType1, OType2} of
{path, edge} -> {convert, OType2, edge_closure, E2b};
{path, edge_closure} when Type1 =:= Type2 -> E2b;
_ -> throw_error({type_error, xref_parser:t2s(Expr)})
@@ -347,7 +347,7 @@ check_expr({regexpr, RExpr, Type0}, _Table) ->
release -> 'R'
end,
Var = {variable, {predef, V}},
- Call = {call, fun(E, V2) -> xref_utils:regexpr(E, V2) end,
+ Call = {call, fun(E, V2) -> xref_utils:regexpr(E, V2) end,
{constants, RExpr}, Var},
{expr, Type, vertex, Call};
check_expr(C={constant, _Type, _OType, _C}, Table) ->
@@ -368,15 +368,15 @@ check_conversion(OType, Type1, Type2, Expr) ->
end.
%% Allowed conversions.
-conversions(_OType, {line, LineType}, {line, LineType}) -> ok;
+conversions(_OType, {line, LineType}, {line, LineType}) -> ok;
conversions(edge, {line, _}, {line, all_line_call}) -> ok;
-conversions(edge, From, {line, Line})
+conversions(edge, From, {line, Line})
when is_atom(From), Line =/= all_line_call -> ok;
conversions(vertex, From, {line, line}) when is_atom(From) -> ok;
conversions(vertex, From, To) when is_atom(From), is_atom(To) -> ok;
conversions(edge, From, To) when is_atom(From), is_atom(To) -> ok;
%% "Extra":
-conversions(edge, {line, Line}, To)
+conversions(edge, {line, Line}, To)
when is_atom(To), Line =/= all_line_call -> ok;
conversions(vertex, {line, line}, To) when is_atom(To) -> ok;
conversions(_OType, _From, _To) -> not_ok.
@@ -399,7 +399,7 @@ ari_op(difference) -> fun(X, Y) -> X - Y end.
restriction(ROp, E1, Type1, NE1, Type2, NE2) ->
{Column, _} = restr_op(ROp),
- case NE1 of
+ case NE1 of
{call, union_of_family, _E} when ROp =:= '|' ->
restriction(Column, Type1, E1, Type2, NE2);
{call, union_of_family, _E} when ROp =:= '||' ->
@@ -455,8 +455,8 @@ check_constants(Cs=[C={constant, Type0, OType, _Con} | Cs1], Table) ->
E = function_vertices_to_family(Type, OType, {constants, S}),
{expr, Type, OType, E};
[{Type1, [C1|_]}, {Type2, [C2|_]} | _] ->
- throw_error({type_mismatch,
- make_vertex(Type1, C1),
+ throw_error({type_mismatch,
+ make_vertex(Type1, C1),
make_vertex(Type2, C2)})
end.
@@ -467,7 +467,7 @@ check_mix([C={constant, Type, OType, _Con} | Cs], Type0, OType, _C0)
check_mix(Cs, Type, OType, C);
check_mix([C | _], _Type0, _OType0, C0) ->
throw_error({type_mismatch, xref_parser:t2s(C0), xref_parser:t2s(C)});
-check_mix([], _Type0, _OType0, _C0) ->
+check_mix([], _Type0, _OType0, _C0) ->
ok.
split(Types, Cs, Table) ->
@@ -478,11 +478,11 @@ split([Type | Types], Vs, AllSoFar, _Type, Table, L) ->
S0 = known_vertices(Type, Vs, Table),
S = difference(S0, AllSoFar),
case is_empty_set(S) of
- true ->
+ true ->
split(Types, Vs, AllSoFar, Type, Table, L);
- false ->
+ false ->
All = union(AllSoFar, S0),
- split(Types, Vs, All, Type, Table,
+ split(Types, Vs, All, Type, Table,
[{Type, to_external(S)} | L])
end;
split([], Vs, All, Type, _Table, L) ->
@@ -491,7 +491,7 @@ split([], Vs, All, Type, _Table, L) ->
[C|_] -> throw_error({unknown_constant, make_vertex(Type, C)})
end.
-make_vertex(Type, C) ->
+make_vertex(Type, C) ->
xref_parser:t2s({constant, Type, vertex, C}).
constant_vertices([{constant, _Type, edge, {A,B}} | Cs], L) ->
@@ -504,7 +504,7 @@ constant_vertices([], L) ->
known_vertices('Fun', Cs, T) ->
M = projection(1, Cs),
F = union_of_family(restriction(fetch_value(v, T), M)),
- intersection(Cs, F);
+ union(bifs(Cs), intersection(Cs, F));
known_vertices('Mod', Cs, T) ->
intersection(Cs, fetch_value('M', T));
known_vertices('App', Cs, T) ->
@@ -512,6 +512,11 @@ known_vertices('App', Cs, T) ->
known_vertices('Rel', Cs, T) ->
intersection(Cs, fetch_value('R', T)).
+bifs(Cs) ->
+ specification({external,
+ fun({M,F,A}) -> xref_utils:is_builtin(M, F, A) end},
+ Cs).
+
function_vertices_to_family(function, vertex, E) ->
{call, partition_family, 1, E};
function_vertices_to_family(_Type, _OType, E) ->
@@ -567,11 +572,11 @@ convert(E, OType, FromType, ToType) ->
general(_ObjectType, FromType, ToType, X) when FromType =:= ToType ->
X;
-general(edge, {line, _LineType}, ToType, LEs) ->
+general(edge, {line, _LineType}, ToType, LEs) ->
VEs = {projection, ?Q({external, fun({V1V2,_Ls}) -> V1V2 end}), LEs},
general(edge, function, ToType, VEs);
general(edge, function, ToType, VEs) ->
- MEs = {projection,
+ MEs = {projection,
?Q({external, fun({{M1,_,_},{M2,_,_}}) -> {M1,M2} end}),
VEs},
general(edge, module, ToType, MEs);
@@ -580,7 +585,7 @@ general(edge, module, ToType, MEs) ->
general(edge, application, ToType, AEs);
general(edge, application, release, AEs) ->
{image, {get, ae}, AEs};
-general(vertex, {line, _LineType}, ToType, L) ->
+general(vertex, {line, _LineType}, ToType, L) ->
V = {partition_family, ?Q(1), {domain, L}},
general(vertex, function, ToType, V);
general(vertex, function, ToType, V) ->
@@ -595,18 +600,18 @@ general(vertex, application, release, A) ->
special(_ObjectType, FromType, ToType, X) when FromType =:= ToType ->
X;
special(edge, {line, _LineType}, {line, all_line_call}, Calls) ->
- {put, ?T(mods),
- {projection,
- ?Q({external, fun({{{M1,_,_},{M2,_,_}},_}) -> {M1,M2} end}),
+ {put, ?T(mods),
+ {projection,
+ ?Q({external, fun({{{M1,_,_},{M2,_,_}},_}) -> {M1,M2} end}),
Calls},
- {put, ?T(def_at),
+ {put, ?T(def_at),
{union, {image, {get, def_at},
- {union, {domain, {get, ?T(mods)}},
+ {union, {domain, {get, ?T(mods)}},
{range, {get, ?T(mods)}}}}},
{fun funs_to_lines/2,
{get, ?T(def_at)}, Calls}}};
special(edge, function, {line, LineType}, VEs) ->
- Var = if
+ Var = if
LineType =:= line -> call_at;
LineType =:= export_call -> e_call_at;
LineType =:= local_call -> l_call_at;
@@ -615,9 +620,9 @@ special(edge, function, {line, LineType}, VEs) ->
line_edges(VEs, Var);
special(edge, module, ToType, MEs) ->
VEs = {image,
- {projection,
+ {projection,
?Q({external, fun(FE={{M1,_,_},{M2,_,_}}) -> {{M1,M2},FE} end}),
- {union,
+ {union,
{image, {get, e},
{projection, ?Q({external, fun({M1,_M2}) -> M1 end}), MEs}}}},
MEs},
@@ -629,7 +634,7 @@ special(edge, release, ToType, REs) ->
AEs = {inverse_image, {get, ae}, REs},
special(edge, application, ToType, AEs);
special(vertex, function, {line, _LineType}, V) ->
- {restriction,
+ {restriction,
{union_of_family, {restriction, {get, def_at}, {domain, V}}},
{union_of_family, V}};
special(vertex, module, ToType, M) ->
@@ -643,15 +648,15 @@ special(vertex, release, ToType, R) ->
special(vertex, application, ToType, A).
line_edges(VEs, CallAt) ->
- {put, ?T(ves), VEs,
- {put, ?T(m1),
- {projection, ?Q({external, fun({{M1,_,_},_}) -> M1 end}),
+ {put, ?T(ves), VEs,
+ {put, ?T(m1),
+ {projection, ?Q({external, fun({{M1,_,_},_}) -> M1 end}),
{get, ?T(ves)}},
{image, {projection, ?Q({external, fun(C={VV,_L}) -> {VV,C} end}),
{union, {image, {get, CallAt}, {get, ?T(m1)}}}},
{get, ?T(ves)}}}}.
-%% {(((v1,l1),(v2,l2)),l) :
+%% {(((v1,l1),(v2,l2)),l) :
%% (v1,l1) in DefAt and (v2,l2) in DefAt and ((v1,v2),L) in CallAt}
funs_to_lines(DefAt, CallAt) ->
T1 = multiple_relative_product({DefAt, DefAt}, projection(1, CallAt)),
@@ -765,7 +770,7 @@ save_vars([], _D, Vs, UVs, L) ->
%% Traverses the expression again, this time using more or less the
%% inverse of the table created by find_nodes. The first time a node
-%% is visited, its children are traversed, the following times a
+%% is visited, its children are traversed, the following times a
%% get instructions are inserted (using the saved value).
make_instructions(N, UserVars, D) ->
{D1, Is0} = make_instrs(N, D, []),
@@ -777,9 +782,9 @@ make_instructions(N, UserVars, D) ->
make_more_instrs([UV | UVs], D, Is) ->
case dict:find(UV, D) of
- error ->
+ error ->
make_more_instrs(UVs, D, Is);
- _Else ->
+ _Else ->
{ND, NIs} = make_instrs(UV, D, Is),
make_more_instrs(UVs, ND, [pop | NIs])
end;
@@ -844,17 +849,17 @@ evaluate([{quote, Val} | P], T, S) ->
evaluate(P, T, [Val | S]);
evaluate([{get, Var} | P], T, S) when is_atom(Var) -> % predefined
Value = fetch_value(Var, T),
- Val = case Value of
+ Val = case Value of
{R, _} -> R; % relation
_ -> Value % simple set
end,
- evaluate(P, T, [Val | S]);
+ evaluate(P, T, [Val | S]);
evaluate([{get, {inverse, Var}} | P], T, S) -> % predefined, inverse
{_, R} = fetch_value(Var, T),
- evaluate(P, T, [R | S]);
+ evaluate(P, T, [R | S]);
evaluate([{get, {user, Var}} | P], T, S) ->
Val = fetch_value(Var, T),
- evaluate(P, T, [Val | S]);
+ evaluate(P, T, [Val | S]);
evaluate([{get, Var} | P], T, S) -> % tmp
evaluate(P, T, [dict:fetch(Var, T) | S]);
evaluate([{save, Var={tmp, _}} | P], T, S=[Val | _]) ->
@@ -862,7 +867,7 @@ evaluate([{save, Var={tmp, _}} | P], T, S=[Val | _]) ->
evaluate(P, dict:store(Var, Val, T1), S);
evaluate([{save, {user, Name}} | P], T, S=[Val | _]) ->
#xref_var{vtype = user, otype = OType, type = Type} = dict:fetch(Name, T),
- NewVar = #xref_var{name = Name, value = Val,
+ NewVar = #xref_var{name = Name, value = Val,
vtype = user, otype = OType, type = Type},
T1 = update_graph_counter(Val, +1, T),
NT = dict:store(Name, NewVar, T1),
@@ -889,7 +894,7 @@ update_graph_counter(Value, Inc, T) ->
error when Inc =:= 1 ->
dict:store(Value, 1, T)
end;
- _EXIT ->
+ _EXIT ->
T
end.
diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl
index db755c31d8..d22f0df164 100644
--- a/lib/tools/src/xref_reader.erl
+++ b/lib/tools/src/xref_reader.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
-module(xref_reader).
@@ -22,7 +22,7 @@
-import(lists, [keysearch/3, member/2, reverse/1]).
--record(xrefr,
+-record(xrefr,
{module=[],
function=[],
def_at=[],
@@ -59,15 +59,15 @@
module(Module, Forms, CollectBuiltins, X, DF) ->
Attrs = [{Attr,V} || {attribute,_Line,Attr,V} <- Forms],
IsAbstract = xref_utils:is_abstract_module(Attrs),
- S = #xrefr{module = Module, builtins_too = CollectBuiltins,
+ S = #xrefr{module = Module, builtins_too = CollectBuiltins,
is_abstr = IsAbstract, x = X, df = DF},
forms(Forms, S).
forms([F | Fs], S) ->
S1 = form(F, S),
forms(Fs, S1);
-forms([], S) ->
- #xrefr{module = M, def_at = DefAt,
+forms([], S) ->
+ #xrefr{module = M, def_at = DefAt,
l_call_at = LCallAt, x_call_at = XCallAt,
el = LC, ex = XC, x = X, df = Depr,
lattrs = AL, xattrs = AX, battrs = B, unresolved = U} = S,
@@ -75,7 +75,7 @@ forms([], S) ->
{ok, M, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr}, U}.
form({attribute, Line, xref, Calls}, S) -> % experimental
- #xrefr{module = M, function = Fun,
+ #xrefr{module = M, function = Fun,
lattrs = L, xattrs = X, battrs = B} = S,
attr(Calls, Line, M, Fun, L, X, B, S);
form({attribute, _Line, _Attr, _Val}, S) ->
@@ -110,12 +110,12 @@ clauses([{clause, _Line, _H, G, B} | Cs], FunVars, Matches, S) ->
S2 = expr(B, S1),
S3 = S2#xrefr{funvars = FunVars, matches = Matches},
clauses(Cs, S3);
-clauses([], _FunVars, _Matches, S) ->
+clauses([], _FunVars, _Matches, S) ->
S.
attr([E={From, To} | As], Ln, M, Fun, AL, AX, B, S) ->
case mfa(From, M) of
- {_, _, MFA} when MFA =:= Fun; [] =:= Fun ->
+ {_, _, MFA} when MFA =:= Fun; [] =:= Fun ->
attr(From, To, Ln, M, Fun, AL, AX, B, S, As, E);
{_, _, _} ->
attr(As, Ln, M, Fun, AL, AX, [E | B], S);
@@ -164,7 +164,7 @@ expr({call, Line,
%% Added in R10B-6. M:F/A.
expr({'fun', Line, {function, Mod, Fun, Arity}}, S);
expr({'fun', Line, {function, Mod, Name, Arity}}, S) ->
- %% Added in R10B-6. M:F/A.
+ %% Added in R10B-6. M:F/A.
As = lists:duplicate(Arity, {atom, Line, foo}),
external_call(Mod, Name, As, Line, false, S);
expr({'fun', Line, {function, Name, Arity}, _Extra}, S) ->
@@ -183,7 +183,7 @@ expr({call, Line, {remote, _Line, Mod, Name}, As}, S) ->
expr({call, Line, F, As}, S) ->
external_call(erlang, apply, [F, list2term(As)], Line, true, S);
expr({match, _Line, {var,_,Var}, {'fun', _, {clauses, Cs}, _Extra}}, S) ->
- %% This is what is needed in R7 to avoid warnings for the functions
+ %% This is what is needed in R7 to avoid warnings for the functions
%% that are passed around by the "expansion" of list comprehension.
S1 = S#xrefr{funvars = [Var | S#xrefr.funvars]},
clauses(Cs, S1);
@@ -192,6 +192,14 @@ expr({match, _Line, {var,_,Var}, E}, S) ->
%% Args = [A,B], apply(m, f, Args)
S1 = S#xrefr{matches = [{Var, E} | S#xrefr.matches]},
expr(E, S1);
+expr({op, _Line, 'orelse', Op1, Op2}, S) ->
+ expr([Op1, Op2], S);
+expr({op, _Line, 'andalso', Op1, Op2}, S) ->
+ expr([Op1, Op2], S);
+expr({op, Line, Op, Operand1, Operand2}, S) ->
+ external_call(erlang, Op, [Operand1, Operand2], Line, false, S);
+expr({op, Line, Op, Operand}, S) ->
+ external_call(erlang, Op, [Operand], Line, false, S);
expr(T, S) when is_tuple(T) ->
expr(tuple_to_list(T), S);
expr([E | Es], S) ->
@@ -241,13 +249,13 @@ external_call(Mod, Fun, ArgsList, Line, X, S) ->
_Else -> % apply2, 1 or 2
check_funarg(W, ArgsList, Line, S1)
end.
-
+
eval_args(Mod, Fun, ArgsTerm, Line, S, ArgsList, Extra) ->
{IsSimpleCall, M, F} = mod_fun(Mod, Fun),
case term2list(ArgsTerm, [], S) of
undefined ->
S1 = unresolved(M, F, -1, Line, S),
- expr(ArgsList, S1);
+ expr(ArgsList, S1);
ArgsList2 when not IsSimpleCall ->
S1 = unresolved(M, F, length(ArgsList2), Line, S),
expr(ArgsList, S1);
@@ -288,14 +296,14 @@ fun_args(apply2, [FunArg, Args]) -> {FunArg, Args};
fun_args(1, [FunArg | Args]) -> {FunArg, Args};
fun_args(2, [_Node, FunArg | Args]) -> {FunArg, Args}.
-list2term([A | As]) ->
+list2term([A | As]) ->
{cons, 0, A, list2term(As)};
-list2term([]) ->
+list2term([]) ->
{nil, 0}.
term2list({cons, _Line, H, T}, L, S) ->
term2list(T, [H | L], S);
-term2list({nil, _Line}, L, _S) ->
+term2list({nil, _Line}, L, _S) ->
reverse(L);
term2list({var, _, Var}, L, S) ->
case keysearch(Var, 1, S#xrefr.matches) of
@@ -332,11 +340,11 @@ handle_call(Locality, To0, Line, S, IsUnres) ->
true ->
S
end,
- case Locality of
- local ->
+ case Locality of
+ local ->
S1#xrefr{el = [Call | S1#xrefr.el],
l_call_at = [CallAt | S1#xrefr.l_call_at]};
- external ->
+ external ->
S1#xrefr{ex = [Call | S1#xrefr.ex],
x_call_at = [CallAt | S1#xrefr.x_call_at]}
end.
diff --git a/lib/tools/test/xref_SUITE.erl b/lib/tools/test/xref_SUITE.erl
index a7855b0bb9..f9d062ef85 100644
--- a/lib/tools/test/xref_SUITE.erl
+++ b/lib/tools/test/xref_SUITE.erl
@@ -39,11 +39,11 @@
-export([all/1, init/1, fini/1]).
-export([xref/1,
- addrem/1, convert/1, intergraph/1, lines/1, loops/1,
+ addrem/1, convert/1, intergraph/1, lines/1, loops/1,
no_data/1, modules/1]).
-export([files/1,
- add/1, default/1, info/1, lib/1, read/1, read2/1, remove/1,
+ add/1, default/1, info/1, lib/1, read/1, read2/1, remove/1,
replace/1, update/1, deprecated/1, trycatch/1,
abstract_modules/1, fun_mfa/1, qlc/1]).
@@ -82,7 +82,7 @@ init(Conf) when is_list(Conf) ->
?line ok = erl_tar:extract(TarFile, [compressed]),
?line ok = file:delete(TarFile),
[{copy_dir, CopyDir} | Conf].
-
+
fini(Conf) when is_list(Conf) ->
%% Nothing.
Conf.
@@ -120,7 +120,7 @@ addrem(Conf) when is_list(Conf) ->
LCallAt_m1 = [],
XCallAt_m1 = [{E1,13}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
D2 = {F2,7},
@@ -132,7 +132,7 @@ addrem(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [{E2,96}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
?line S5 = set_up(S2),
@@ -142,7 +142,7 @@ addrem(Conf) when is_list(Conf) ->
?line {ok, XMod2, S6a} = remove_module(S6, m2),
?line [a2] = XMod2#xref_mod.app_name,
?line S7 = set_up(S6a),
-
+
?line AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
?line S9 = add_application(S7, AppInfo1),
?line S10 = set_up(S9),
@@ -186,7 +186,7 @@ convert(Conf) when is_list(Conf) ->
LCallAt_m1 = [],
XCallAt_m1 = [{E1,13},{E2,17},{E4,7}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
D2 = {F2,7},
@@ -200,7 +200,7 @@ convert(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [{E3,96},{E6,12},{UE1,77}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
D4 = {F4,6},
@@ -213,7 +213,7 @@ convert(Conf) when is_list(Conf) ->
LCallAt_m3 = [{E5,19}],
XCallAt_m3 = [{UE2,22}],
Info3 = #xref_mod{name = m3, app_name = [a3]},
- ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
+ ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
XC_m3, LC_m3),
Info4 = #xref_mod{name = m4, app_name = [a2]},
@@ -303,7 +303,7 @@ convert(Conf) when is_list(Conf) ->
?line {ok, _} = eval("(XXL) (Lin) (Fun) E", AllCallAt, S),
?line {ok, _} = eval("(XXL) (XXL) (Lin) (Fun) E", AllCallAt, S),
- ?line {ok, _} = eval(f("(XXL) (Lin) ~p", [[E1, E6]]),
+ ?line {ok, _} = eval(f("(XXL) (Lin) ~p", [[E1, E6]]),
[{{D1,D3},[13]}, {{D7,D4},[12]}], S),
?line {ok, _} = eval(f("(Fun) ~p", [AllMs]), AllE, S),
?line {ok, _} = eval("(Fun) [m1->m2,m2->m3]", [E1,E2,E6], S),
@@ -323,7 +323,7 @@ intergraph(Conf) when is_list(Conf) ->
F3 = {m1,f3,3},
F4 = {m1,f4,4},
F5 = {m1,f5,5},
-
+
F6 = {m2,f1,6}, % X
F7 = {m2,f1,7},
F8 = {m2,f1,8},
@@ -339,7 +339,7 @@ intergraph(Conf) when is_list(Conf) ->
E5 = {F4,F2},
E6 = {F5,F4},
E7 = {F4,F5},
-
+
E8 = {F6,F7},
E9 = {F7,F8},
E10 = {F8,F1}, % X
@@ -363,9 +363,9 @@ intergraph(Conf) when is_list(Conf) ->
LCallAt_m1 = [{E1,1},{E2,2},{E3,3},{E5,5},{E6,6},{E7,7}],
XCallAt_m1 = [{E1,4}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
-
+
D6 = {F6,6},
D7 = {F7,7},
D8 = {F8,8},
@@ -380,7 +380,7 @@ intergraph(Conf) when is_list(Conf) ->
LCallAt_m2 = [{E8,8},{E9,9},{E11,11},{E12,12},{E13,13},{E14,14}],
XCallAt_m2 = [{E10,10},{E15,15}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
@@ -397,13 +397,13 @@ intergraph(Conf) when is_list(Conf) ->
?line {ok, _} = eval("EE | m2", [{F6,F1}], S),
?line {ok, _} = eval("EE | m2 + EE | m2", [{F6,F1}], S),
- ?line {ok, _} = eval("(Fun)(Lin)(E | m1)",
+ ?line {ok, _} = eval("(Fun)(Lin)(E | m1)",
to_external(union(set(XC_m1), set(LC_m1))), S),
- ?line {ok, _} = eval("(XXL)(ELin) (EE | m1)",
- [{{D2,D1},[1,2,4]},{{D4,D2},[5]},{{D5,D4},[6]},{{D4,D5},[7]}],
+ ?line {ok, _} = eval("(XXL)(ELin) (EE | m1)",
+ [{{D2,D1},[1,2,4]},{{D4,D2},[5]},{{D5,D4},[6]},{{D4,D5},[7]}],
S),
?line {ok, _} = eval("(XXL)(ELin)(EE | m2)", [{{D6,D1},[8,11,12]}], S),
- ?line {ok, _} = eval("(XXL)(ELin)(ELin)(EE | m2)",
+ ?line {ok, _} = eval("(XXL)(ELin)(ELin)(EE | m2)",
[{{D6,D1},[8,11,12]}], S),
%% Combining graphs (equal or different):
@@ -420,15 +420,15 @@ intergraph(Conf) when is_list(Conf) ->
?line {ok, _} = eval("EE | m1 + E | m1", LC_m1, S),
?line {ok, _} = eval(f("EE | ~p + E | ~p", [F2, F2]), [E1,E2], S),
%% [1,4] from 'calls' is a subset of [1,2,4] from Inter Call Graph:
- ?line {ok, _} = eval(f("(XXL)(Lin) (E | ~p)", [F2]),
+ ?line {ok, _} = eval(f("(XXL)(Lin) (E | ~p)", [F2]),
[{{D2,D1},[1,4]},{{D2,D3},[2]}], S),
- ?line {ok, _} = eval(f("(XXL)(ELin) (EE | ~p)", [F2]),
+ ?line {ok, _} = eval(f("(XXL)(ELin) (EE | ~p)", [F2]),
[{{D2,D1},[1,2,4]}], S),
?line {ok, _} = eval(f("(XXL)((ELin)(EE | ~p) + (Lin)(E | ~p))", [F2, F2]),
[{{D2,D1},[1,2,4]},{{D2,D3},[2]}], S),
- ?line {ok, _} =
- eval(f("(XXL)((ELin) ~p + (Lin) ~p)", [{F2, F1}, {F2, F1}]),
+ ?line {ok, _} =
+ eval(f("(XXL)((ELin) ~p + (Lin) ~p)", [{F2, F1}, {F2, F1}]),
[{{D2,D1},[1,2,4]}], S),
?line {ok, _} = eval(f("(Fun)(Lin) ~p", [{F2, F1}]), [E1], S),
%% The external call E4 is included in the reply:
@@ -438,7 +438,7 @@ intergraph(Conf) when is_list(Conf) ->
%% The local call E1 is included in the reply:
?line {ok, _} = eval("(XXL)(Lin)(XC | m1)", [{{D2,D1},[1,4]}], S),
- ?line {ok, _} = eval(f("(LLin) (E | ~p || ~p) + (XLin) (E | ~p || ~p)",
+ ?line {ok, _} = eval(f("(LLin) (E | ~p || ~p) + (XLin) (E | ~p || ~p)",
[F2, F1, F2, F1]), [{E4,[1,4]}], S),
?line {ok, _} = eval("# (ELin) E", 6, S),
@@ -449,7 +449,7 @@ lines(suite) -> [];
lines(doc) -> ["More test of Inter Call Graph, and regular expressions"];
lines(Conf) when is_list(Conf) ->
S0 = new(),
-
+
F1 = {m1,f1,1}, % X
F2 = {m1,f2,2},
F3 = {m1,f3,3},
@@ -464,14 +464,14 @@ lines(Conf) when is_list(Conf) ->
E5 = {F2,F4}, % X
E6 = {F5,F6},
E7 = {F6,F4}, % X
-
+
D1 = {F1,1},
D2 = {F2,2},
D3 = {F3,3},
D4 = {F4,4},
D5 = {F5,5},
D6 = {F6,6},
-
+
DefAt_m1 = [D1,D2,D3,D5,D6],
X_m1 = [F1,F5],
% L_m1 = [F2,F3,F6],
@@ -480,7 +480,7 @@ lines(Conf) when is_list(Conf) ->
LCallAt_m1 = [{E1,1},{E3,3},{E6,6}],
XCallAt_m1 = [{E2,2},{E4,4},{E5,5},{E7,7}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
DefAt_m2 = [D4],
@@ -491,9 +491,9 @@ lines(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
-
+
AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
?line S5 = add_application(S2, AppInfo1),
AppInfo2 = #xref_app{name = a2, rel_name = [r1]},
@@ -509,10 +509,10 @@ lines(Conf) when is_list(Conf) ->
{{D5,D4},[6]}], S),
?line {ok, _} = eval("(XXL)(Lin) (E | m1)",
[{{D1,D2},[1]},{{D1,D4},[4]},{{D2,D1},[2]},
- {{D2,D4},[5]},{{D3,D2},[3]},{{D5,D6},[6]},{{D6,D4},[7]}],
+ {{D2,D4},[5]},{{D3,D2},[3]},{{D5,D6},[6]},{{D6,D4},[7]}],
S),
?line {ok, _} = eval("(E | m1) + (EE | m1)",
- [E1,E2,E3,E4,E5,E6,E7,{F1,F1},{F3,F1},{F3,F4},{F5,F4}],
+ [E1,E2,E3,E4,E5,E6,E7,{F1,F1},{F3,F1},{F3,F4},{F5,F4}],
S),
?line {ok, _} = eval("(Lin)(E | m1)",
[{E4,[4]},{E1,[1]},{E2,[2]},{E5,[5]},
@@ -567,7 +567,7 @@ lines(Conf) when is_list(Conf) ->
loops(suite) -> [];
loops(doc) -> ["More Inter Call Graph, loops and \"unusual\" cases"];
loops(Conf) when is_list(Conf) ->
- S0 = new(),
+ S0 = new(),
F1 = {m1,f1,1}, % X
F2 = {m1,f2,2},
@@ -582,7 +582,7 @@ loops(Conf) when is_list(Conf) ->
E3 = {F3,F4},
E4 = {F4,F5},
E5 = {F5,F3}, % X
-
+
D1 = {F1,1},
D2 = {F2,2},
D3 = {F3,3},
@@ -598,7 +598,7 @@ loops(Conf) when is_list(Conf) ->
LCallAt_m1 = [{E2,2},{E3,3},{E4,4}],
XCallAt_m1 = [{E1,1},{E5,5}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
?line S = set_up(S1),
@@ -659,16 +659,16 @@ modules(Conf) when is_list(Conf) ->
?line {ok, y} = compile:file(Y, [debug_info, {outdir,EB1_1}]),
?line {ok, S0} = xref_base:new([{xref_mode, modules}]),
- ?line {ok, release2, S1} =
+ ?line {ok, release2, S1} =
xref_base:add_release(S0, Dir, [{name,release2}]),
?line S = set_up(S1),
?line {{error, _, {unavailable_analysis, undefined_function_calls}}, _} =
xref_base:analyze(S, undefined_function_calls),
- ?line {{error, _, {unavailable_analysis, locals_not_used}}, _} =
+ ?line {{error, _, {unavailable_analysis, locals_not_used}}, _} =
xref_base:analyze(S, locals_not_used),
- ?line {{error, _, {unavailable_analysis, {call, foo}}}, _} =
+ ?line {{error, _, {unavailable_analysis, {call, foo}}}, _} =
xref_base:analyze(S, {call, foo}),
- ?line {{error, _, {unavailable_analysis, {use, foo}}}, _} =
+ ?line {{error, _, {unavailable_analysis, {use, foo}}}, _} =
xref_base:analyze(S, {use, foo}),
?line analyze(undefined_functions, [{x,undef,0}], S),
?line 5 = length(xref_base:info(S)),
@@ -681,7 +681,7 @@ modules(Conf) when is_list(Conf) ->
ok.
files(suite) ->
- [add, default, info, lib, read, read2, remove, replace, update,
+ [add, default, info, lib, read, read2, remove, replace, update,
deprecated, trycatch, abstract_modules, fun_mfa, qlc].
add(suite) -> [];
@@ -708,7 +708,7 @@ add(Conf) when is_list(Conf) ->
{unix, _} ->
?line make_udir(UDir),
?line make_ufile(UFile);
- _ ->
+ _ ->
true
end,
@@ -743,20 +743,20 @@ add(Conf) when is_list(Conf) ->
xref_base:add_release(S, foo, [{builtins,not_a_value}]),
?line {error, _, {invalid_filename,{foo,bar}}} =
xref_base:add_release(S, {foo,bar}, []),
- ?line {ok, S1} =
+ ?line {ok, S1} =
xref_base:set_default(S, [{verbose,false}, {warnings, false}]),
?line case os:type() of
{unix, _} ->
- ?line {error, _, {file_error, _, _}} =
+ ?line {error, _, {file_error, _, _}} =
xref_base:add_release(S, UDir);
_ ->
true
end,
- ?line {error, _, {file_error, _, _}} =
+ ?line {error, _, {file_error, _, _}} =
xref_base:add_release(S, fname(["/a/b/c/d/e/f","__foo"])),
- ?line {ok, release2, S2} =
+ ?line {ok, release2, S2} =
xref_base:add_release(S1, Dir, [{name,release2}]),
- ?line {error, _, {module_clash, {x, _, _}}} =
+ ?line {error, _, {module_clash, {x, _, _}}} =
xref_base:add_module(S2, Xbeam),
?line {ok, S3} = xref_base:remove_release(S2, release2),
?line {ok, rel2, S4} = xref_base:add_release(S3, Dir),
@@ -764,11 +764,11 @@ add(Conf) when is_list(Conf) ->
xref_base:add_release(S4, Dir),
?line {ok, S5} = xref_base:remove_release(S4, rel2),
%% One unreadable file and one JAM file found (no verification here):
- ?line {ok, [], S6} = xref_base:add_directory(S5, fname(CopyDir,"dir"),
+ ?line {ok, [], S6} = xref_base:add_directory(S5, fname(CopyDir,"dir"),
[{recurse,true}, {warnings,true}]),
?line case os:type() of
{unix, _} ->
- ?line {error, _, {file_error, _, _}} =
+ ?line {error, _, {file_error, _, _}} =
xref_base:add_directory(S6, UDir);
_ ->
true
@@ -803,7 +803,7 @@ default(Conf) when is_list(Conf) ->
xref_base:set_default(S, [not_an_option]),
?line D = xref_base:get_default(S),
- ?line [{builtins,false},{recurse,false},{verbose,false},{warnings,true}] =
+ ?line [{builtins,false},{recurse,false},{verbose,false},{warnings,true}] =
D,
?line ok = xref_base:delete(S),
@@ -831,7 +831,7 @@ info(Conf) when is_list(Conf) ->
?line {error, _, {no_such_info, release}} = xref:info(s, release),
?line {error, _, {no_such_info, release}} = xref:info(s, release, rel),
?line {error, _, {no_such_module, mod}} = xref:info(s, modules, mod),
- ?line {error, _, {no_such_application, app}} =
+ ?line {error, _, {no_such_application, app}} =
xref:info(s, applications, app),
?line {error, _, {no_such_release, rel}} = xref:info(s, releases, rel),
?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
@@ -845,9 +845,9 @@ info(Conf) when is_list(Conf) ->
?line [{rel2,_}] = xref:info(s, releases, rel2),
?line {error, _, {no_such_library, foo}} = xref:info(s, libraries, [foo]),
- ?line {ok, lib1} =
+ ?line {ok, lib1} =
compile:file(fname(LDir,lib1),[debug_info,{outdir,LDir}]),
- ?line {ok, lib2} =
+ ?line {ok, lib2} =
compile:file(fname(LDir,lib2),[debug_info,{outdir,LDir}]),
?line ok = xref:set_library_path(s, [LDir], [{verbose,false}]),
?line [{lib1,_}, {lib2, _}] = xref:info(s, libraries),
@@ -883,13 +883,13 @@ lib(Conf) when is_list(Conf) ->
xref:set_library_path(s, ["foo"], [not_an_option]),
?line {error, _, {invalid_path,otp}} = xref:set_library_path(s,otp),
?line {error, _, {invalid_path,[""]}} = xref:set_library_path(s,[""]),
- ?line {error, _, {invalid_path,[[$a | $b]]}} =
+ ?line {error, _, {invalid_path,[[$a | $b]]}} =
xref:set_library_path(s,[[$a | $b]]),
?line {error, _, {invalid_path,[otp]}} = xref:set_library_path(s,[otp]),
?line {ok, []} = xref:get_library_path(s),
?line ok = xref:set_library_path(s, [Dir], [{verbose,false}]),
?line {ok, UnknownFunctions} = xref:q(s, "U"),
- ?line [{lib1,unknown,0}, {lib2,local,0},
+ ?line [{lib1,unknown,0}, {lib2,local,0},
{lib2,unknown,0}, {unknown,unknown,0}]
= UnknownFunctions,
?line {ok, [{lib2,f,0},{lib3,f,0}]} = xref:q(s, "DF"),
@@ -934,7 +934,7 @@ lib(Conf) when is_list(Conf) ->
?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
?line {ok, cp} = xref:add_module(s, fname(Dir,"cp.beam")),
?line {ok, [{lists, sort, 1}]} = xref:q(s, "U"),
- ?line ok = xref:set_library_path(s, code_path),
+ ?line ok = xref:set_library_path(s, code_path),
?line {ok, []} = xref:q(s, "U"),
?line check_state(s),
?line xref:stop(s),
@@ -1010,18 +1010,18 @@ do_read(File, Version) ->
?line {ok, CallsB} = xref:q(s, "(Lin) (E - UC) "),
?line ok = check_state(s),
?line {ok, XU} = xref:q(s, "XU"),
- ?line Erl = set([{erlang,length,1},{erlang,integer,1},
+ ?line Erl = set([{erlang,length,1},{erlang,integer,1},
{erlang,binary_to_term,1}]),
- ?line [{erlang,binary_to_term,1},{erlang,length,1}] =
+ ?line [{erlang,binary_to_term,1},{erlang,length,1}] =
to_external(intersection(set(XU), Erl)),
- ?line xref:stop(s).
+ ?line xref:stop(s).
%% What is expected when xref_SUITE_data/read/read.erl is added:
read_expected(Version) ->
%% Line positions in xref_SUITE_data/read/read.erl:
- POS1 = 28, POS2 = POS1+10, POS3 = POS2+6, POS4 = POS3+6, POS5 = POS4+10,
- POS6 = POS5+5, POS7 = POS6+6, POS8 = POS7+6, POS9 = POS8+8,
- POS10 = POS9+10, POS11 = POS10+7, POS12 = POS11+8, POS13 = POS12+10,
+ POS1 = 28, POS2 = POS1+10, POS3 = POS2+6, POS4 = POS3+6, POS5 = POS4+10,
+ POS6 = POS5+5, POS7 = POS6+6, POS8 = POS7+6, POS9 = POS8+8,
+ POS10 = POS9+10, POS11 = POS10+7, POS12 = POS11+8, POS13 = POS12+10,
POS14 = POS13+18, % POS15 = POS14+23,
FF = {read,funfuns,0},
@@ -1162,7 +1162,7 @@ read_expected(Version) ->
{POS14+17,{{read,bi,0},{read,bi,0}}}],
OK = case Version of
- abstract_v1 ->
+ abstract_v1 ->
[{POS8+3, {FF,{erlang,apply,3}}},
{POS10+1, {FF,{erlang,apply,3}}},
{POS10+6, {FF,{erlang,apply,3}}}]
@@ -1170,7 +1170,7 @@ read_expected(Version) ->
[{0,{FF,{read,'$F_EXPR',178}}},
{0,{FF,{modul,'$F_EXPR',179}}}]
++ O1;
- _ ->
+ _ ->
% [{POS15+2,{{read,bi,0},{foo,t,0}}},
% {POS15+3,{{read,bi,0},{bar,t,0}}},
% {POS15+6,{{read,bi,0},{read,local,0}}},
@@ -1183,18 +1183,34 @@ read_expected(Version) ->
end,
%% When builtins =:= true:
- OKB = [{POS13+1,{FF,{erts_debug,apply,4}}},
- {POS13+2,{FF,{erts_debug,apply,4}}},
- {POS13+3,{FF,{erts_debug,apply,4}}},
- {POS1+3, {FF,{erlang,binary_to_term,1}}},
- {POS3+1, {FF,{erlang,spawn,3}}},
- {POS3+2, {FF,{erlang,spawn,3}}},
- {POS3+3, {FF,{erlang,spawn_link,3}}},
- {POS3+4, {FF,{erlang,spawn_link,3}}},
- {POS6+4, {FF,{erlang,spawn,3}}},
- {POS13+5, {{read,bi,0},{erlang,length,1}}},
- {POS14+3, {{read,bi,0},{erlang,length,1}}}]
- ++ OK,
+ OKB1 = [{POS13+1,{FF,{erts_debug,apply,4}}},
+ {POS13+2,{FF,{erts_debug,apply,4}}},
+ {POS13+3,{FF,{erts_debug,apply,4}}},
+ {POS1+3, {FF,{erlang,binary_to_term,1}}},
+ {POS3+1, {FF,{erlang,spawn,3}}},
+ {POS3+2, {FF,{erlang,spawn,3}}},
+ {POS3+3, {FF,{erlang,spawn_link,3}}},
+ {POS3+4, {FF,{erlang,spawn_link,3}}},
+ {POS6+4, {FF,{erlang,spawn,3}}},
+ {POS13+5, {{read,bi,0},{erlang,length,1}}},
+ {POS14+3, {{read,bi,0},{erlang,length,1}}}],
+
+ %% Operators (OTP-8647):
+ OKB = case Version of
+ abstract_v1 ->
+ [];
+ _ ->
+ [{POS13+16, {{read,bi,0},{erlang,'!',2}}},
+ {POS13+16, {{read,bi,0},{erlang,'-',1}}},
+ {POS13+16, {{read,bi,0},{erlang,self,0}}}]
+ end
+ ++ [{POS14+19, {{read,bi,0},{erlang,'+',2}}},
+ {POS14+21, {{read,bi,0},{erlang,'+',2}}},
+ {POS13+16, {{read,bi,0},{erlang,'==',2}}},
+ {POS14+15, {{read,bi,0},{erlang,'==',2}}},
+ {POS13+5, {{read,bi,0},{erlang,'>',2}}},
+ {POS14+3, {{read,bi,0},{erlang,'>',2}}}]
+ ++ OKB1 ++ OK,
{U, OK, OKB}.
@@ -1217,9 +1233,9 @@ read2(Conf) when is_list(Conf) ->
spawn_opt(fun() -> foo end, [link]),
spawn_opt(f(),
{read2,f}, [{min_heap_size,1000}]),
- spawn_opt(f(),
+ spawn_opt(f(),
fun() -> f() end, [flopp]),
- spawn_opt(f(),
+ spawn_opt(f(),
read2, f, [], []);
f() ->
%% Duplicated unresolved calls are ignored:
@@ -1237,7 +1253,7 @@ read2(Conf) when is_list(Conf) ->
?line {ok, U2} = xref:q(s, "(Lin) UC"),
?line {ok, OK2} = xref:q(s, "(Lin) (E - UC)"),
?line true = U =:= U2,
- ?line true = OK =:= OK2,
+ ?line true = OK =:= OK2,
?line ok = check_state(s),
?line xref:stop(s),
@@ -1304,7 +1320,7 @@ replace(Conf) when is_list(Conf) ->
?line {ok, true} = xref:set_default(s, warnings, false),
?line {ok, rel2} = xref:add_release(s, Dir, []),
?line {error, _, _} = xref:replace_application(s, app1, "no_data"),
- ?line {error, _, {no_such_application, app12}} =
+ ?line {error, _, {no_such_application, app12}} =
xref:replace_application(s, app12, A1_0, []),
?line {error, _, {invalid_filename,{foo,bar}}} =
xref:replace_application(s, app1, {foo,bar}, []),
@@ -1312,7 +1328,7 @@ replace(Conf) when is_list(Conf) ->
xref:replace_application(s, foo, bar, [not_an_option]),
?line {error, _, {invalid_options,[{builtins,not_a_value}]}} =
xref:replace_application(s, foo, bar, [{builtins,not_a_value}]),
- ?line {ok, app1} =
+ ?line {ok, app1} =
xref:replace_application(s, app1, A1_0),
?line [{_, AppInfo}] = xref:info(s, applications, app1),
?line {value, {release, [rel2]}} = keysearch(release, 1, AppInfo),
@@ -1332,14 +1348,14 @@ replace(Conf) when is_list(Conf) ->
?line {ok, x} = compile:file(X, [no_debug_info, {outdir,EB1_1}]),
?line {error, _, {no_debug_info, _}} = xref:replace_module(s, x, Xbeam),
- ?line {error, _, {module_mismatch, x,y}} =
+ ?line {error, _, {module_mismatch, x,y}} =
xref:replace_module(s, x, Ybeam),
?line case os:type() of
{unix, _} ->
?line hide_file(Ybeam),
- ?line {error, _, {file_error, _, _}} =
+ ?line {error, _, {file_error, _, _}} =
xref:replace_module(s, x, Ybeam);
- _ ->
+ _ ->
true
end,
?line ok = xref:remove_module(s, x),
@@ -1362,16 +1378,16 @@ update(Conf) when is_list(Conf) ->
Source = fname(Dir, "x.erl"),
Beam = fname(Dir, "x.beam"),
?line copy_file(fname(Dir, "x.erl.1"), Source),
- ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
-
+ ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
+
?line {ok, _} = start(s),
- ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
?line {ok, [x]} = xref:add_directory(s, Dir, [{builtins,true}]),
?line {error, _, {invalid_options,[not_an_option]}} =
xref:update(s, [not_an_option]),
?line {ok, []} = xref:update(s),
?line {ok, [{erlang,atom_to_list,1}]} = xref:q(s, "XU"),
-
+
?line [{x, ModInfo}] = xref:info(s, modules, x),
?line case keysearch(directory, 1, ModInfo) of
{value, {directory, Dir}} -> ok
@@ -1379,7 +1395,7 @@ update(Conf) when is_list(Conf) ->
timer:sleep(2000), % make sure modification time has changed
?line copy_file(fname(Dir, "x.erl.2"), Source),
- ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
+ ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
?line {ok, [x]} = xref:update(s, []),
?line {ok, [{erlang,list_to_atom,1}]} = xref:q(s, "XU"),
@@ -1454,11 +1470,11 @@ deprecated(Conf) when is_list(Conf) ->
DF = usort(DF_3++[{{M9,t,0},{M9,f,1}}]),
?line {ok,DF} = xref:analyze(s, deprecated_function_calls),
- ?line {ok,DF_1} =
+ ?line {ok,DF_1} =
xref:analyze(s, {deprecated_function_calls,next_version}),
- ?line {ok,DF_2} =
+ ?line {ok,DF_2} =
xref:analyze(s, {deprecated_function_calls,next_major_release}),
- ?line {ok,DF_3} =
+ ?line {ok,DF_3} =
xref:analyze(s, {deprecated_function_calls,eventually}),
D = to_external(range(from_term(DF))),
@@ -1467,11 +1483,11 @@ deprecated(Conf) when is_list(Conf) ->
D_3 = to_external(range(from_term(DF_3))),
?line {ok,D} = xref:analyze(s, deprecated_functions),
- ?line {ok,D_1} =
+ ?line {ok,D_1} =
xref:analyze(s, {deprecated_functions,next_version}),
- ?line {ok,D_2} =
+ ?line {ok,D_2} =
xref:analyze(s, {deprecated_functions,next_major_release}),
- ?line {ok,D_3} =
+ ?line {ok,D_3} =
xref:analyze(s, {deprecated_functions,eventually}),
?line ok = check_state(s),
@@ -1516,11 +1532,11 @@ deprecated(Conf) when is_list(Conf) ->
DFa = DFa_3,
?line {ok,DFa} = xref:analyze(s, deprecated_function_calls),
- ?line {ok,DFa_1} =
+ ?line {ok,DFa_1} =
xref:analyze(s, {deprecated_function_calls,next_version}),
- ?line {ok,DFa_2} =
+ ?line {ok,DFa_2} =
xref:analyze(s, {deprecated_function_calls,next_major_release}),
- ?line {ok,DFa_3} =
+ ?line {ok,DFa_3} =
xref:analyze(s, {deprecated_function_calls,eventually}),
?line ok = check_state(s),
@@ -1564,11 +1580,11 @@ deprecated(Conf) when is_list(Conf) ->
DFb = usort(DFb_2++[{{M,bar,2},{M,t,0}},{{M,g,3},{M,bar,2}}]),
?line {ok,DFb} = xref:analyze(s, deprecated_function_calls),
- ?line {ok,DFb_1} =
+ ?line {ok,DFb_1} =
xref:analyze(s, {deprecated_function_calls,next_version}),
- ?line {ok,DFb_2} =
+ ?line {ok,DFb_2} =
xref:analyze(s, {deprecated_function_calls,next_major_release}),
- ?line {ok,DFb_3} =
+ ?line {ok,DFb_3} =
xref:analyze(s, {deprecated_function_calls,eventually}),
?line ok = check_state(s),
@@ -1599,7 +1615,7 @@ trycatch(Conf) when is_list(Conf) ->
catch
error:a -> err:e1();
error:b -> err:e2()
- after
+ after
fini:shed()
end.
">>,
@@ -1616,7 +1632,7 @@ trycatch(Conf) when is_list(Conf) ->
{{{A,A,0},{err,e2,0}},[13]},
{{{A,A,0},{fini,shed,0}},[15]},
{{{A,A,0},{foo,bar,0}},[7]},
- {{{A,A,0},{foo,foo,0}},[9]}]} =
+ {{{A,A,0},{foo,foo,0}},[9]}]} =
xref:q(s, "(Lin) (E | trycatch:trycatch/0)"),
?line ok = check_state(s),
@@ -1662,7 +1678,7 @@ abstract_modules(Conf) when is_list(Conf) ->
{{{A,args,1},{A,local,1}},[6]},
{{{A,args,1},{A,new,2}},[8]},
{{{A,local,1},{A,module_info,1}},[12]},
- {{{param,new,2},{param,instance,2}},[0]}]} =
+ {{{param,new,2},{param,instance,2}},[0]}]} =
xref:q(s, "(Lin) E"),
?line {ok,[{param,args,1},
{param,instance,2},
@@ -1747,10 +1763,10 @@ qlc(Conf) when is_list(Conf) ->
t() ->
dets:open_file(t, []),
dets:insert(t, [{1,a},{2,b},{3,c},{4,d}]),
- MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X < 5) -> {Y}
+ MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X < 5) -> {Y}
end),
QH1 = dets:table(t, [{traverse, {select, MS}}]),
- QH2 = qlc:q([{Y} || {X,Y} <- dets:table(t),
+ QH2 = qlc:q([{Y} || {X,Y} <- dets:table(t),
(X > 1) or (X < 5)]),
true = qlc:info(QH1) =:= qlc:info(QH2),
dets:close(t),
@@ -1783,7 +1799,7 @@ analyze(Conf) when is_list(Conf) ->
xref_base:analyze(S0, undefined_function_calls, [not_an_option]),
?line {{error, _, {invalid_query,{q}}}, _} = xref_base:q(S0,{q}),
?line {{error, _, {unknown_analysis,foo}}, _} = xref_base:analyze(S0, foo),
- ?line {{error, _, {unknown_constant,"foo:bar/-1"}}, _} =
+ ?line {{error, _, {unknown_constant,"foo:bar/-1"}}, _} =
xref_base:analyze(S0, {use,{foo,bar,-1}}),
CopyDir = ?copydir,
@@ -1803,30 +1819,30 @@ analyze(Conf) when is_list(Conf) ->
?line {ok, rel2, S1} = xref_base:add_release(S0, Dir, [{verbose,false}]),
?line S = set_up(S1),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze(undefined_function_calls, [{{x,xx,0},{x,undef,0}}], S),
?line {ok, _} = analyze(undefined_functions, [{x,undef,0}], S),
?line {ok, _} = analyze(locals_not_used, [{x,l,0},{x,l1,0}], S),
?line {ok, _} = analyze(exports_not_used, [{x,xx,0},{y,t,0}], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze(deprecated_function_calls, [{{y,t,0},{x,t,0}}], S),
?line {ok, _} = analyze({deprecated_function_calls,next_version}, [], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze({deprecated_function_calls,next_major_release}, [], S),
- ?line {ok, _} = analyze({deprecated_function_calls,eventually},
+ ?line {ok, _} = analyze({deprecated_function_calls,eventually},
[{{y,t,0},{x,t,0}}], S),
?line {ok, _} = analyze(deprecated_functions, [{x,t,0}], S),
?line {ok, _} = analyze({deprecated_functions,next_version}, [], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze({deprecated_functions,next_major_release}, [], S),
?line {ok, _} = analyze({deprecated_functions,eventually}, [{x,t,0}], S),
?line {ok, _} = analyze({call, {x,xx,0}}, [{x,undef,0}], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze({call, [{x,xx,0},{x,l,0}]}, [{x,l1,0},{x,undef,0}], S),
?line {ok, _} = analyze({use, {x,l,0}}, [{x,l1,0}], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze({use, [{x,l,0},{x,l1,0}]}, [{x,l,0},{x,l1,0}], S),
?line {ok, _} = analyze({module_call, x}, [x], S),
@@ -1881,7 +1897,7 @@ basic(Conf) when is_list(Conf) ->
LCallAt_m1 = [{E7,12}],
XCallAt_m1 = [{E1,13},{E2,17},{E4,7}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
D2 = {F2,7},
@@ -1895,7 +1911,7 @@ basic(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [{E3,96},{E6,12},{UE1,77}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
D4 = {F4,6},
@@ -1908,7 +1924,7 @@ basic(Conf) when is_list(Conf) ->
LCallAt_m3 = [{E5,19}],
XCallAt_m3 = [{UE2,22}],
Info3 = #xref_mod{name = m3, app_name = [a3]},
- ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
+ ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
XC_m3, LC_m3),
Info4 = #xref_mod{name = m4, app_name = [a2]},
@@ -1955,7 +1971,7 @@ basic(Conf) when is_list(Conf) ->
?line {ok, _} = eval(f("(Mod) ~p", [[F1,F6,F5]]), [m1,m3], S),
?line {ok, _} = eval("(Lin) M - (Lin) m1",
[{F2,7},{F3,9},{F7,19},{F4,6},{F5,97},{UF2,0}], S),
- ?line {ok, _} = eval(f("(Lin) M * (Lin) ~p", [[F1,F6]]),
+ ?line {ok, _} = eval(f("(Lin) M * (Lin) ~p", [[F1,F6]]),
[{F1,12},{F6,3}], S),
?line {ok, _} = eval(f("X * ~p", [[F1, F2, F3, F4, F5]]), [F3, F4], S),
@@ -1976,7 +1992,7 @@ basic(Conf) when is_list(Conf) ->
?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]),
[{{D1,D3},[13]},{{D1,D4},[7]}],S),
?line {ok, _} = eval(f("XC | (~p + ~p)", [F1, F2]), [E1,E4,E3,UE1], S),
- ?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]),
+ ?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]),
[{{D1,D3},[13]},{{D1,D4},[7]}], S),
?line {ok, _} = eval("LC | m3", [E5], S),
?line {ok, _} = eval(f("LC | ~p", [F1]), [E7], S),
@@ -1984,7 +2000,7 @@ basic(Conf) when is_list(Conf) ->
?line {ok, _} = eval("E | m1", [E1,E2,E4,E7], S),
?line {ok, _} = eval(f("E | ~p", [F1]), [E1,E7,E4], S),
?line {ok, _} = eval(f("E | (~p + ~p)", [F1, F2]), [E1,E7,E4,E3,UE1], S),
-
+
?line {ok, _} = eval("XC || m1", [E3,UE2], S),
?line {ok, _} = eval(f("XC || ~p", [F6]), [E3], S),
?line {ok, _} = eval(f("XC || (~p + ~p)", [F4, UF2]), [UE1,E4,E6], S),
@@ -2012,18 +2028,18 @@ basic(Conf) when is_list(Conf) ->
?line {ok, _} = eval("components V", type_error, S),
?line {ok, _} = eval("components E + components E", type_error, S),
- ?line {ok, _} = eval(f("range (closure E | ~p)", [[F1,F2]]),
+ ?line {ok, _} = eval(f("range (closure E | ~p)", [[F1,F2]]),
[F6,F3,F7,F4,F5,UF1,UF2], S),
- ?line {ok, _} =
+ ?line {ok, _} =
eval(f("domain (closure E || ~p)", [[UF2,F7]]), [F1,F2,F6], S),
?line {ok, _} = eval("components E", [], S),
?line {ok, _} = eval("components (Mod) E", [[m1,m2,m3]], S),
?line {ok, _} = eval("components closure (Mod) E", [[m1,m2,m3]], S),
- ?line {ok, _} = eval("condensation (Mod) E",
+ ?line {ok, _} = eval("condensation (Mod) E",
[{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
- ?line {ok, _} = eval("condensation closure (Mod) E",
+ ?line {ok, _} = eval("condensation closure (Mod) E",
[{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
- ?line {ok, _} = eval("condensation closure closure closure (Mod) E",
+ ?line {ok, _} = eval("condensation closure closure closure (Mod) E",
[{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
?line {ok, _} = eval("weak condensation (Mod) E",
[{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]},{[m17],[m17]}], S),
@@ -2035,11 +2051,11 @@ basic(Conf) when is_list(Conf) ->
[[m1,m2,m3]], S),
%% |, ||, |||
- ?line {ok, _} = eval("(Lin) E || V", type_error, S),
- ?line {ok, _} = eval("E ||| (Lin) V", type_error, S),
+ ?line {ok, _} = eval("(Lin) E || V", type_error, S),
+ ?line {ok, _} = eval("E ||| (Lin) V", type_error, S),
?line {ok, _} = eval("E ||| m1", [E7], S),
?line {ok, _} = eval("closure E ||| m1", [E7,{F1,UF1},{F6,UF1}], S),
- ?line {ok, _} = eval("closure E ||| [m1,m2]",
+ ?line {ok, _} = eval("closure E ||| [m1,m2]",
[{F1,UF1},{F2,F7},{F1,F7},{F6,UF1},{F2,UF1},{F7,UF1},E7,E1,E2,E3], S),
?line {ok, _} = eval("AE | a1", [{a1,a1},{a1,a2},{a1,a3}], S),
@@ -2095,7 +2111,7 @@ md(Conf) when is_list(Conf) ->
Y = fname(Dir, "y__y.erl"),
Xbeam = fname(Dir, "x__x.beam"),
Ybeam = fname(Dir, "y__y.beam"),
-
+
?line {error, _, {invalid_filename,{foo,bar}}} = xref:m({foo,bar}),
?line {error, _, {invalid_filename,{foo,bar}}} = xref:d({foo,bar}),
@@ -2171,7 +2187,7 @@ variables(Conf) when is_list(Conf) ->
LCallAt_m1 = [],
XCallAt_m1 = [{E1,13},{E3,17}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
D2 = {F2,7},
@@ -2183,11 +2199,11 @@ variables(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [{E2,96}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
?line S = set_up(S2),
-
+
?line eval("T1=E, T2=E*T1, T3 = T2*T2, T4=range T3, T5=T3|T4, T5",
[E1,E2,E3], S),
?line eval("((E*E)*(E*E)) | (range ((E*E)*(E*E)))",
@@ -2202,16 +2218,16 @@ variables(Conf) when is_list(Conf) ->
?line {ok, S102} = eval("T2 := E | m2", [E2], S101),
?line {{ok, [{user, ['T0', 'T1', 'T2']}]}, _} = xref_base:variables(S102),
?line {ok, S103} = xref_base:forget(S102, 'T0'),
- ?line {{ok, [{user, ['T1', 'T2']}]}, S104} =
+ ?line {{ok, [{user, ['T1', 'T2']}]}, S104} =
xref_base:variables(S103, [user]),
?line {ok, S105} = xref_base:forget(S104),
?line {{ok, [{user, []}]}, S106} = xref_base:variables(S105),
- ?line {{ok, [{predefined,_}]}, S107_0} =
+ ?line {{ok, [{predefined,_}]}, S107_0} =
xref_base:variables(S106, [predefined]),
- ?line {ok, S107_1} =
+ ?line {ok, S107_1} =
eval("TT := E, TT2 := V, TT1 := TT * TT", [E1,E2,E3], S107_0),
- ?line {{ok, [{user, ['TT', 'TT1', 'TT2']}]}, _} =
+ ?line {{ok, [{user, ['TT', 'TT1', 'TT2']}]}, _} =
xref_base:variables(S107_1),
?line {ok, S107} = xref_base:forget(S107_1),
@@ -2220,14 +2236,14 @@ variables(Conf) when is_list(Conf) ->
Beam = fname(Dir, "lib1.beam"),
?line copy_file(fname(Dir, "lib1.erl"), Beam),
- ?line {ok, S108} =
+ ?line {ok, S108} =
xref_base:set_library_path(S107, [Dir], [{verbose,false}]),
?line {{error, _, _}, _} = xref_base:variables(S108, [{verbose,false}]),
?line {ok, S109} = xref_base:set_library_path(S108, [], [{verbose,false}]),
?line Tabs = length(ets:all()),
- ?line {ok, S110} = eval("Eplus := closure E, TT := Eplus",
+ ?line {ok, S110} = eval("Eplus := closure E, TT := Eplus",
'closure()', S109),
?line {{ok, [{user, ['Eplus','TT']}]}, S111} = xref_base:variables(S110),
?line {ok, S112} = xref_base:forget(S111, ['TT','Eplus']),
@@ -2289,7 +2305,7 @@ unused_locals(Conf) when is_list(Conf) ->
?line {ok, []} = xref:analyse(s, locals_not_used),
?line ok = check_state(s),
?line xref:stop(s),
-
+
?line ok = file:delete(File1),
?line ok = file:delete(Beam1),
?line ok = file:delete(File2),
@@ -2303,7 +2319,7 @@ format_error(suite) -> [];
format_error(doc) -> ["Format error messages"];
format_error(Conf) when is_list(Conf) ->
?line {ok, _Pid} = start(s),
- ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
%% Parse error messages.
?line "Invalid regular expression \"add(\"" ++ _ =
@@ -2332,7 +2348,7 @@ format_error(Conf) when is_list(Conf) ->
%% Other messages
?line 'Variable \'QQ\' used before set\n' =
fatom(xref:q(s,"QQ")),
- ?line 'Unknown constant a\n' =
+ ?line 'Unknown constant a\n' =
fatom(xref:q(s,"{a} of E")),
%% Testing xref_parser:t2s/1.
@@ -2341,12 +2357,12 @@ format_error(Conf) when is_list(Conf) ->
?line 'Variable assigned more than once: E = E + E\n' =
fatom(xref:q(s,"E=E + E")),
?line "Operator applied to argument(s) of different or invalid type(s): "
- "E + V * V\n" =
+ "E + V * V\n" =
flatten(xref:format_error(xref:q(s,"E + (V * V)"))),
?line {error,xref_compiler,{type_error,"(V + V) * E"}} =
xref:q(s,"(V + V) * E"),
?line "Type does not match structure of constant: [m:f/3 -> g:h/17] : "
- "App\n" =
+ "App\n" =
flatten(xref:format_error(xref:q(s,"[{{m,f,3},{g,h,17}}] : App"))),
?line 'Type does not match structure of constant: [m -> f, g -> h] : Fun\n'
= fatom(xref:q(s,"[{m,f},g->h] : Fun")),
@@ -2360,11 +2376,11 @@ format_error(Conf) when is_list(Conf) ->
xref:q(s,"condensation (# E + # V)"),
?line {error,xref_compiler,{type_error,"range (# E + # E)"}} =
xref:q(s,"range (#E + #E)"),
- ?line {error,xref_compiler,{type_error,"range (# E)"}} =
+ ?line {error,xref_compiler,{type_error,"range (# E)"}} =
xref:q(s,"range #E"), % Hm...
?line {error,xref_compiler,{type_error,"E + # E"}} =
xref:q(s,"E + #E + #E"), % Hm...
- ?line {error,xref_compiler,{type_error,"V * E || V | V"}} =
+ ?line {error,xref_compiler,{type_error,"V * E || V | V"}} =
xref:q(s,"V * (E || V) | V"),
?line {error,xref_compiler,{type_error,"E || (E | V)"}} =
xref:q(s,"V * E || (E | V)"),
@@ -2421,7 +2437,7 @@ eval(Query, E, S) ->
?format("------------------------------~n", []),
?format("Evaluating ~p~n", [Query]),
?line {Answer, NewState} = xref_base:q(S, Query, [{verbose, false}]),
- {Reply, Expected} =
+ {Reply, Expected} =
case Answer of
{ok, R} when is_list(E) ->
{unsetify(R), sort(E)};
@@ -2430,7 +2446,7 @@ eval(Query, E, S) ->
{error, _Module, Reason} ->
{element(1, Reason), E}
end,
- if
+ if
Reply =:= Expected ->
?format("As expected, got ~n~p~n", [Expected]),
{ok, NewState};
@@ -2442,7 +2458,7 @@ eval(Query, E, S) ->
analyze(Query, E, S) ->
?format("------------------------------~n", []),
?format("Evaluating ~p~n", [Query]),
- ?line {{ok, L}, NewState} =
+ ?line {{ok, L}, NewState} =
xref_base:analyze(S, Query, [{verbose, false}]),
case {unsetify(L), sort(E)} of
{X,X} ->
@@ -2461,7 +2477,7 @@ unsetify(S) ->
%% Note: assumes S has been set up; the new state is not returned
eval(Query, S) ->
- ?line {{ok, Answer}, _NewState} =
+ ?line {{ok, Answer}, _NewState} =
xref_base:q(S, Query, [{verbose, false}]),
unsetify(Answer).
@@ -2514,7 +2530,7 @@ check_state(S) ->
functions_mode_check(S, Info)
end.
-%% The manual mentions some facts that should always hold.
+%% The manual mentions some facts that should always hold.
%% Here they are again.
functions_mode_check(S, Info) ->
%% F = L + X,
@@ -2526,7 +2542,7 @@ functions_mode_check(S, Info) ->
?line {ok, V} = xref:q(S, "X + L + B + U"),
%% X, L, B and U are disjoint.
- ?line {ok, []} =
+ ?line {ok, []} =
xref:q(S, "X * L + X * B + X * U + L * B + L * U + B * U"),
%% V = UU + XU + LU,
@@ -2577,11 +2593,11 @@ functions_mode_check(S, Info) ->
?line {Local, Exported} = info(Info, no_functions),
?line LX = Local+Exported,
- ?line {ok, LXs} = xref:q(S, 'Extra = _:module_info/"(0|1)" + LM,
+ ?line {ok, LXs} = xref:q(S, 'Extra = _:module_info/"(0|1)" + LM,
# (F - Extra)'),
?line true = LX =:= LXs,
- ?line {LocalCalls, ExternalCalls, UnresCalls} =
+ ?line {LocalCalls, ExternalCalls, UnresCalls} =
info(Info, no_function_calls),
?line LEU = LocalCalls + ExternalCalls + UnresCalls,
?line {ok, LEU} = xref:q(S, "# LC + # XC"),
@@ -2635,7 +2651,7 @@ check_count(S) ->
%% {ok, A} = xref:q(S, 'A'),
{ok, M} = xref:q(S, 'AM'),
- {ok, _} = xref:q(S,
+ {ok, _} = xref:q(S,
"Extra := _:module_info/\"(0|1)\" + LM"),
%% info/1:
@@ -2670,7 +2686,7 @@ check_count(S) ->
ok.
info_module([M | Ms], S) ->
- {ok, NoCalls} = per_module("T = (E | ~p : Mod), # (XLin) T + # (LLin) T",
+ {ok, NoCalls} = per_module("T = (E | ~p : Mod), # (XLin) T + # (LLin) T",
M, S),
{ok, NoFunCalls} = per_module("# (E | ~p : Mod)", M, S),
{ok, NoXCalls} = per_module("# (XC | ~p : Mod)", M, S),
@@ -2719,14 +2735,14 @@ start(Server) ->
end.
add_erts_code_path(KernelPath) ->
- VersionDirs =
+ VersionDirs =
filelib:is_dir(
filename:join(
[code:lib_dir(),
lists:flatten(
["kernel-",
- [X ||
- {kernel,_,X} <-
+ [X ||
+ {kernel,_,X} <-
application_controller:which_applications()]])])),
case VersionDirs of
true ->
@@ -2746,5 +2762,5 @@ add_erts_code_path(KernelPath) ->
[KernelPath]
end
end.
-
-
+
+
diff --git a/lib/tools/test/xref_SUITE_data/read/read.erl b/lib/tools/test/xref_SUITE_data/read/read.erl
index 4a0cc280c3..19694c9e25 100644
--- a/lib/tools/test/xref_SUITE_data/read/read.erl
+++ b/lib/tools/test/xref_SUITE_data/read/read.erl
@@ -106,13 +106,13 @@ funfuns() ->
apply(m,f,a), % {m,f,-1}
3(a), % {'$M_EXPR','$F_EXPR',1}
apply(3,[a]), % {'$M_EXPR','$F_EXPR',1}
-
+
%% POS12=POS11+8
apply(A, A), % number of arguments is not known, {'$M_EXPR','$F_EXPR',-1}
Args0 = [list],
Args = [a | Args0], % number of arguments is known
apply(A, Args), % {'$M_EXPR','$F_EXPR',2}
- apply(m3, f3, Args), %
+ apply(m3, f3, Args), %
NotArgs = [is_not, a | list], % number of arguments is not known
apply(A, NotArgs), % {'$M_EXPR','$F_EXPR',-1}
apply(m4, f4, NotArgs), % {m4,f4,-1}
@@ -125,7 +125,7 @@ funfuns() ->
bi() when length([]) > 17 ->
foo:module_info(),
module_info(),
- A = tjo,
+ A = true andalso tjo ,
t:foo(A),
case true of
true when integer(1) ->
@@ -133,7 +133,7 @@ bi() when length([]) > 17 ->
false ->
X = flopp
end,
- X == A;
+ self() ! X == -A orelse false;
bi() ->
%% POS14=POS13+18
Z = fun(Y) -> Y end,
@@ -159,7 +159,7 @@ bi() ->
D + E + F.
%bi() ->
% %% POS15=POS14+13
-% try
+% try
% foo:t(),
% bar:t()
% of
@@ -169,7 +169,7 @@ bi() ->
% foo:t()
% catch
% {'EXIT',_} -> bar:t()
-% end.
+% end.
local() ->
true.