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         for (size_t off, len; off < dst.length; off += len) {
117             len = socket.receive(dst[off..$]);
118             enforceEx!MYX(len != 0, "Server closed the connection");
119             enforceEx!MYX(len != socket.ERROR, "Received std.socket.Socket.ERROR");
120         }
121     }
122 
123     void write(in ubyte[] bytes)
124     {
125         socket.send(bytes);
126     }
127 
128     void acquire() { /+ Do nothing +/ }
129     void release() { /+ Do nothing +/ }
130     bool isOwner() { return true; }
131     bool amOwner() { return true; }
132 }
133 
134 // Wraps a Vibe.d socket with the common interface
135 version(Have_vibe_d) {
136     package class MySQLSocketVibeD : MySQLSocket
137     {
138         private PlainVibeDSocket socket;
139 
140         // The socket should already be open
141         this(PlainVibeDSocket socket)
142         {
143             enforceEx!MYX(socket, "Tried to use a null Vibe.d socket - Maybe the 'openSocket' callback returned null?");
144             enforceEx!MYX(socket.connected, "Tried to use a closed Vibe.d socket - Maybe the 'openSocket' callback created a socket but forgot to open it?");
145             this.socket = socket;
146         }
147 
148         invariant()
149         {
150             assert(!!socket);
151         }
152 
153         void close()
154         {
155             socket.close();
156         }
157 
158         @property bool connected() const
159         {
160             return socket.connected;
161         }
162 
163         void read(ubyte[] dst)
164         {
165             socket.read(dst);
166         }
167 
168         void write(in ubyte[] bytes)
169         {
170             socket.write(bytes);
171         }
172 
173         static if (is(typeof(&TCPConnection.isOwner))) {
174             void acquire() { socket.acquire(); }
175             void release() { socket.release(); }
176             bool isOwner() { return socket.isOwner(); }
177             bool amOwner() { return socket.isOwner(); }
178         } else {
179             void acquire() { /+ Do nothing +/ }
180             void release() { /+ Do nothing +/ }
181             bool isOwner() { return true; }
182             bool amOwner() { return true; }
183         }
184     }
185 }