1 module mysql.common;
2 
3 import std.algorithm;
4 import std.conv;
5 import std.datetime;
6 import std.digest.sha;
7 import std.exception;
8 import std.range;
9 import std.socket;
10 import std.stdio;
11 import std.string;
12 import std.traits;
13 import std.variant;
14 
15 version(Have_vibe_d)
16 {
17     static if(__traits(compiles, (){ import vibe.core.net; } ))
18         import vibe.core.net;
19     else
20         static assert(false, "mysql-native can't find Vibe.d's 'vibe.core.net'.");
21 }
22 
23 /**
24  * An exception type to distinguish exceptions thrown by this module.
25  */
26 class MySQLException: Exception
27 {
28     this(string msg, string file = __FILE__, size_t line = __LINE__) pure
29     {
30         super(msg, file, line);
31     }
32 }
33 alias MySQLException MYX;
34 
35 /**
36  * Received invalid data from the server which violates the MySQL network protocol.
37  */
38 class MySQLProtocolException: MySQLException
39 {
40     this(string msg, string file, size_t line) pure
41     {
42         super(msg, file, line);
43     }
44 }
45 alias MySQLProtocolException MYXProtocol;
46 
47 // Phobos/Vibe.d type aliases
48 package alias std.socket.TcpSocket PlainPhobosSocket;
49 version(Have_vibe_d)
50 {
51     package alias vibe.core.net.TCPConnection PlainVibeDSocket;
52 }
53 else
54 {
55     // Dummy types
56     package alias Object PlainVibeDSocket;
57 }
58 
59 alias PlainPhobosSocket function(string,ushort) OpenSocketCallbackPhobos;
60 alias PlainVibeDSocket  function(string,ushort) OpenSocketCallbackVibeD;
61 
62 enum MySQLSocketType { phobos, vibed }
63 
64 // A minimal socket interface similar to Vibe.d's TCPConnection.
65 // Used to wrap both Phobos and Vibe.d sockets with a common interface.
66 package interface MySQLSocket
67 {
68     void close();
69     @property bool connected() const;
70     void read(ubyte[] dst);
71     void write(in ubyte[] bytes);
72 
73     void acquire();
74     void release();
75     bool isOwner();
76     bool amOwner();
77 }
78 
79 // Wraps a Phobos socket with the common interface
80 package class MySQLSocketPhobos : MySQLSocket
81 {
82     private PlainPhobosSocket socket;
83 
84     // The socket should already be open
85     this(PlainPhobosSocket socket)
86     {
87         enforceEx!MYX(socket, "Tried to use a null Phobos socket - Maybe the 'openSocket' callback returned null?");
88         enforceEx!MYX(socket.isAlive, "Tried to use a closed Phobos socket - Maybe the 'openSocket' callback created a socket but forgot to open it?");
89         this.socket = socket;
90     }
91 
92     invariant()
93     {
94         assert(!!socket);
95     }
96 
97     void close()
98     {
99         socket.shutdown(SocketShutdown.BOTH);
100         socket.close();
101     }
102 
103     @property bool connected() const
104     {
105         return socket.isAlive;
106     }
107 
108     void read(ubyte[] dst)
109     {
110         // Note: I'm a little uncomfortable with this line as it doesn't
111         // (and can't) update Connection._open. Not sure what can be done,
112         // but perhaps Connection._open should be eliminated in favor of
113         // querying the socket's opened/closed state directly.
114         scope(failure) socket.close();
115 
116         auto bytesRead = socket.receive(dst);
117         enforceEx!MYX(bytesRead == dst.length, "Wrong number of bytes read");
118         enforceEx!MYX(bytesRead != socket.ERROR, "Received std.socket.Socket.ERROR");
119     }
120 
121     void write(in ubyte[] bytes)
122     {
123         socket.send(bytes);
124     }
125 
126     void acquire() { /+ Do nothing +/ }
127     void release() { /+ Do nothing +/ }
128     bool isOwner() { return true; }
129     bool amOwner() { return true; }
130 }
131 
132 // Wraps a Vibe.d socket with the common interface
133 version(Have_vibe_d) {
134     package class MySQLSocketVibeD : MySQLSocket
135     {
136         private PlainVibeDSocket socket;
137 
138         // The socket should already be open
139         this(PlainVibeDSocket socket)
140         {
141             enforceEx!MYX(socket, "Tried to use a null Vibe.d socket - Maybe the 'openSocket' callback returned null?");
142             enforceEx!MYX(socket.connected, "Tried to use a closed Vibe.d socket - Maybe the 'openSocket' callback created a socket but forgot to open it?");
143             this.socket = socket;
144         }
145 
146         invariant()
147         {
148             assert(!!socket);
149         }
150 
151         void close()
152         {
153             socket.close();
154         }
155 
156         @property bool connected() const
157         {
158             return socket.connected;
159         }
160 
161         void read(ubyte[] dst)
162         {
163             socket.read(dst);
164         }
165 
166         void write(in ubyte[] bytes)
167         {
168             socket.write(bytes);
169         }
170 
171         static if (is(typeof(&TCPConnection.isOwner))) {
172             void acquire() { socket.acquire(); }
173             void release() { socket.release(); }
174             bool isOwner() { return socket.isOwner(); }
175             bool amOwner() { return socket.isOwner(); }
176         } else {
177             void acquire() { /+ Do nothing +/ }
178             void release() { /+ Do nothing +/ }
179             bool isOwner() { return true; }
180             bool amOwner() { return true; }
181         }
182     }
183 }