Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ISC Open Source Projects
Kea
Commits
5b1850bb
Commit
5b1850bb
authored
Nov 23, 2012
by
Stephen Morris
Browse files
[2404] Tidying up and refactoring the code
parent
3b2c2c14
Changes
6
Expand all
Hide whitespace changes
Inline
Side-by-side
src/lib/dhcpsrv/lease_mgr.cc
View file @
5b1850bb
...
...
@@ -31,13 +31,13 @@ using namespace std;
using
namespace
isc
::
dhcp
;
Lease6
::
Lease6
(
LeaseType
type
,
const
isc
::
asiolink
::
IOAddress
&
addr
,
DuidPtr
duid
,
uint32_t
iaid
,
uint32_t
preferred
,
uint32_t
valid
,
uint32_t
t1
,
uint32_t
t2
,
SubnetID
subnet_id
,
uint8_t
prefixlen
)
:
type_
(
type
),
addr_
(
addr
),
prefixlen_
(
prefixlen
),
iaid_
(
iaid
),
duid_
(
duid
),
preferred_lft_
(
preferred
),
valid_lft_
(
valid
),
t1_
(
t1
),
t2_
(
t2
),
subnet_id_
(
subnet_id
),
fixed_
(
false
),
fqdn_fwd_
(
false
),
fqdn_rev_
(
false
)
{
Lease6
::
Lease6
(
LeaseType
type
,
const
isc
::
asiolink
::
IOAddress
&
addr
,
DuidPtr
duid
,
uint32_t
iaid
,
uint32_t
preferred
,
uint32_t
valid
,
uint32_t
t1
,
uint32_t
t2
,
SubnetID
subnet_id
,
uint8_t
prefixlen
)
:
addr_
(
addr
),
type_
(
type
),
prefixlen_
(
prefixlen
),
iaid_
(
iaid
),
duid_
(
duid
),
preferred_lft_
(
preferred
),
valid_lft_
(
valid
),
t1_
(
t1
),
t2_
(
t2
),
subnet_id_
(
subnet_id
),
fixed_
(
false
),
fqdn_fwd_
(
false
),
fqdn_rev_
(
false
)
{
if
(
!
duid
)
{
isc_throw
(
InvalidOperation
,
"DUID must be specified for a lease"
);
}
...
...
src/lib/dhcpsrv/lease_mgr.h
View file @
5b1850bb
...
...
@@ -87,6 +87,13 @@ public:
isc
::
Exception
(
file
,
line
,
what
)
{}
};
/// @brief Multiple lease records found where one expected
class
MultipleRecords
:
public
Exception
{
public:
MultipleRecords
(
const
char
*
file
,
size_t
line
,
const
char
*
what
)
:
isc
::
Exception
(
file
,
line
,
what
)
{}
};
/// @brief Attempt to update lease that was not there
class
NoSuchLease
:
public
Exception
{
public:
...
...
@@ -101,6 +108,7 @@ public:
/// would be required. As this is a critical part of the code that will be used
/// extensively, direct access is warranted.
struct
Lease4
{
/// @brief Constructor
///
/// @param addr IPv4 address as unsigned 32-bit integer in network byte
...
...
@@ -133,69 +141,78 @@ struct Lease4 {
/// @brief Address extension
///
/// It is envisaged that in some cases IPv4 address will be accompanied with some
/// additional data. One example of such use are Address + Port solutions (or
/// Port-restricted Addresses), where several clients may get the same address, but
/// different port ranges. This feature is not expected to be widely used.
/// Under normal circumstances, the value should be 0.
/// It is envisaged that in some cases IPv4 address will be accompanied
/// with some additional data. One example of such use are Address + Port
/// solutions (or Port-restricted Addresses), where several clients may get
/// the same address, but different port ranges. This feature is not
/// expected to be widely used. Under normal circumstances, the value
/// should be 0.
uint32_t
ext_
;
/// @brief
h
ardware address
/// @brief
H
ardware address
std
::
vector
<
uint8_t
>
hwaddr_
;
/// @brief client identifier
/// @brief Client identifier
///
/// @todo Should this be a pointer to a client ID or the ID itself?
/// Compare with the DUID in the Lease6 structure.
boost
::
shared_ptr
<
ClientId
>
client_id_
;
/// @brief
r
enewal timer
/// @brief
R
enewal timer
///
/// Specifies renewal time. Although technically it is a property of
IA container,
/// not the address itself, since our data model does not
define separate IA
/// entity, we are keeping it in the lease. In
case of multiple addresses/prefixes
///
for the same IA, each must have consistent T1 and T2 values. Specified in
/// seconds since cltt.
/// Specifies renewal time. Although technically it is a property of
the
///
IA container and
not the address itself, since our data model does not
///
define a separate IA
entity, we are keeping it in the lease. In
the
///
case of multiple addresses/prefixes for the same IA, each must have
///
consistent T1 and T2 values. This is specified in
seconds since cltt.
uint32_t
t1_
;
/// @brief
r
ebinding timer
/// @brief
R
ebinding timer
///
/// Specifies rebinding time. Although technically it is a property of
IA container,
/// not the address itself, since our data model does not
define separate IA
/// entity, we are keeping it in the lease. In
case of multiple addresses/prefixes
///
for the same IA, each must have consistent T1 and T2 values. Specified in
/// seconds since cltt.
/// Specifies rebinding time. Although technically it is a property of
the
///
IA container and
not the address itself, since our data model does not
///
define a separate IA
entity, we are keeping it in the lease. In
the
///
case of multiple addresses/prefixes for the same IA, each must have
///
consistent T1 and T2 values. This is pecified in
seconds since cltt.
uint32_t
t2_
;
/// @brief
v
alid lifetime
/// @brief
R
alid lifetime
///
/// Expressed as number of seconds since cltt
/// Expressed as number of seconds since cltt
.
uint32_t
valid_lft_
;
/// @brief
c
lient last transmission time
/// @brief
C
lient last transmission time
///
/// Specifies a timestamp, when last transmission from a client was received.
/// Specifies a timestamp giving the time when the last transmission from a
/// client was received.
time_t
cltt_
;
/// @brief Subnet identifier
///
/// Specifies
subnet-id
of the subnet t
hat
the lease belongs
to
/// Specifies
the identification
of the subnet t
o which
the lease belongs
.
SubnetID
subnet_id_
;
/// @brief
Is this a f
ixed lease?
/// @brief
F
ixed lease?
///
/// Fixed leases are kept after they are released/expired.
bool
fixed_
;
/// @brief
c
lient hostname
/// @brief
C
lient hostname
///
/// This field may be empty
std
::
string
hostname_
;
/// @brief did we update AAAA record for this lease?
/// @brief Forward zone updated?
///
/// Set true if the DNS AAAA record for this lease has been updated.
bool
fqdn_fwd_
;
/// @brief did we update PTR record for this lease?
/// @brief Reverse zone updated?
///
/// Set true if the DNS PTR record for this lease has been updated.
bool
fqdn_rev_
;
/// @brief Lease comments
.
/// @brief Lease comments
///
/// Currently not used. It may be used for keeping comments made by the
/// system administrator.
...
...
@@ -210,13 +227,16 @@ typedef boost::shared_ptr<Lease4> Lease4Ptr;
/// @brief A collection of IPv4 leases.
typedef
std
::
vector
<
Lease4Ptr
>
Lease4Collection
;
/// @brief Structure that holds a lease for IPv6 address and/or prefix
///
/// For performance reasons it is a simple structure, not a class.
Had
we chose
to
/// make it a class, all fields would have to
be
made private and getters/setters
/// For performance reasons it is a simple structure, not a class.
If
we chose
/// make it a class, all fields would have to made private and getters/setters
/// would be required. As this is a critical part of the code that will be used
/// extensively, direct access rather than through getters/setters is warranted.
struct
Lease6
{
/// @brief Type of lease contents
typedef
enum
{
LEASE_IA_NA
,
/// the lease contains non-temporary IPv6 address
LEASE_IA_TA
,
/// the lease contains temporary IPv6 address
...
...
@@ -228,86 +248,96 @@ struct Lease6 {
uint32_t
iaid
,
uint32_t
preferred
,
uint32_t
valid
,
uint32_t
t1
,
uint32_t
t2
,
SubnetID
subnet_id
,
uint8_t
prefixlen_
=
0
);
/// @brief specifies lease type (normal addr, temporary addr, prefix)
LeaseType
type_
;
/// IPv6 address
/// @brief IPv6 address
isc
::
asiolink
::
IOAddress
addr_
;
/// IPv6 prefix length (used only for PD)
/// @brief Lease type
///
/// One of normal address, temporary address, or prefix.
LeaseType
type_
;
/// @brief IPv6 prefix length
///
/// This is used only for prefix delegations and is ignored otherwise.
uint8_t
prefixlen_
;
/// @brief IAID
/// @brief
Identity Association Identifier (
IAID
)
///
///
Identity Association IDentifier.
DHCPv6 stores all addresses and prefixes
///
in IA containers (IA_NA,
IA_TA, IA_PD). Most containers may appear more
///
than once in a message.
To differentiate between them, IAID field is present
/// DHCPv6 stores all addresses and prefixes
in IA containers (IA_NA,
/// IA_TA, IA_PD). Most containers may appear more
than once in a message.
/// To differentiate between them,
the
IAID field is present
uint32_t
iaid_
;
/// @brief
c
lient identifier
/// @brief
C
lient identifier
boost
::
shared_ptr
<
DUID
>
duid_
;
/// @brief preferred lifetime
///
/// This parameter specifies preferred lifetime since the lease was
assigned/renewed
/// (cltt), expressed in seconds.
/// This parameter specifies
the
preferred lifetime since the lease was
///
assigned or renewed
(cltt), expressed in seconds.
uint32_t
preferred_lft_
;
/// @brief valid lifetime
///
/// This parameter specifie
d
valid lifetime since the lease wa
s assigned/renewed
/// (cltt), expressed in seconds.
/// This parameter specifie
s the
valid lifetime since the lease wa
a
///
assigned/renewed
(cltt), expressed in seconds.
uint32_t
valid_lft_
;
/// @brief T1 timer
///
/// Specifies renewal time. Although technically it is a property of
IA container,
/// not the address itself, since our data model does not
define separate IA
/// entity, we are keeping it in the lease. In
case of multiple addresses/prefixes
///
for the same IA, each must have consistent T1 and T2 values. Specified in
/// seconds since cltt.
/// Th
is
value will also be useful for failover to calculate the next
expected
/// client transmission time.
/// Specifies renewal time. Although technically it is a property of
the
///
IA container and
not the address itself, since our data model does not
///
define a separate IA
entity, we are keeping it in the lease. In
the
///
case of multiple addresses/prefixes for the same IA, each must have
///
consistent T1 and T2 values. This is specified in
seconds since cltt.
/// Th
e
value will also be useful for failover to calculate the next
///
expected
client transmission time.
uint32_t
t1_
;
/// @brief T2 timer
///
/// Specifies rebinding time. Although technically it is a property of
IA container,
/// not the address itself, since our data model does not
define separate IA
/// entity, we are keeping it in the lease. In
case of multiple addresses/prefixes
///
for the same IA, each must have consistent T1 and T2 values. Specified in
/// seconds since cltt.
/// Specifies rebinding time. Although technically it is a property of
the
///
IA container and
not the address itself, since our data model does not
///
define a separate IA
entity, we are keeping it in the lease. In
the
///
case of multiple addresses/prefixes for the same IA, each must have
///
consistent T1 and T2 values. This is specified in
seconds since cltt.
uint32_t
t2_
;
/// @brief
c
lient last transmission time
/// @brief
C
lient last transmission time
///
/// Specifies a timestamp, when last transmission from a client was received.
/// Specifies a timestamp giving the time when the last transmission from a
/// client was received.
time_t
cltt_
;
/// @brief Subnet identifier
///
/// Specifies
subnet-id
of the subnet t
hat
the lease belongs
to
/// Specifies
the identification
of the subnet t
o which
the lease belongs
.
SubnetID
subnet_id_
;
/// @brief
Is this a f
ixed lease?
/// @brief
F
ixed lease?
///
/// Fixed leases are kept after they are released/expired.
bool
fixed_
;
/// @brief
c
lient hostname
/// @brief
C
lient hostname
///
/// This field may be empty
std
::
string
hostname_
;
/// @brief did we update AAAA record for this lease?
/// @brief Forward zone updated?
///
/// Set true if the DNS AAAA record for this lease has been updated.
bool
fqdn_fwd_
;
/// @brief did we update PTR record for this lease?
/// @brief Reverse zone updated?
///
/// Set true if the DNS PTR record for this lease has been updated.
bool
fqdn_rev_
;
/// @brief Lease comments
///
/// This field is currently not used.
/// Currently not used. It may be used for keeping comments made by the
/// system administrator.
std
::
string
comments_
;
/// @todo: Add DHCPv6 failover related fields here
...
...
@@ -357,7 +387,7 @@ typedef std::vector<Lease6Ptr> Lease6Collection;
/// see the documentation of those classes for details.
class
LeaseMgr
{
public:
/// Client
H
ardware address
/// Client
h
ardware address
typedef
std
::
vector
<
uint8_t
>
HWAddr
;
/// Database configuration parameter map
...
...
src/lib/dhcpsrv/mysql_lease_mgr.cc
View file @
5b1850bb
This diff is collapsed.
Click to expand it.
src/lib/dhcpsrv/mysql_lease_mgr.h
View file @
5b1850bb
...
...
@@ -28,7 +28,7 @@ namespace dhcp {
// Define the current database schema values
const
uint32_t
CURRENT_VERSION_VERSION
=
0
;
const
uint32_t
CURRENT_VERSION_MINOR
=
1
;
const
uint32_t
CURRENT_VERSION_MINOR
=
2
;
// Forward declaration of the Lease exchange objects. This class is defined
...
...
@@ -69,7 +69,7 @@ public:
/// @brief Destructor (closes database)
virtual
~
MySqlLeaseMgr
();
/// @brief Adds an IPv4 lease
.
/// @brief Adds an IPv4 lease
///
/// @param lease lease to be added
///
...
...
@@ -80,7 +80,7 @@ public:
/// failed.
virtual
bool
addLease
(
const
Lease4Ptr
&
lease
);
/// @brief Adds an IPv6 lease
.
/// @brief Adds an IPv6 lease
///
/// @param lease lease to be added
///
...
...
@@ -210,13 +210,22 @@ public:
/// @brief Updates IPv4 lease.
///
/// Updates the record of the lease in the database (as identified by the
/// address) with the data in the passed lease object.
///
/// @param lease4 The lease to be updated.
///
/// If no such lease is present, an exception will be thrown.
/// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
/// exist.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
virtual
void
updateLease4
(
const
Lease4Ptr
&
lease4
);
/// @brief Updates IPv6 lease.
///
/// Updates the record of the lease in the database (as identified by the
/// address) with the data in the passed lease object.
///
/// @param lease6 The lease to be updated.
///
/// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
...
...
@@ -234,6 +243,9 @@ public:
/// @param addr IPv4 address of the lease to be deleted.
///
/// @return true if deletion was successful, false if no such lease exists
///
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
virtual
bool
deleteLease4
(
const
isc
::
asiolink
::
IOAddress
&
addr
);
/// @brief Deletes an IPv6 lease.
...
...
@@ -409,7 +421,8 @@ private:
/// @brief Add Lease Common Code
///
/// This method performs the common actions for both flavours of the
/// addLease method.
/// addLease method. It binds the contents of the lease object to
/// the prepated statement and adds it to the database.
///
/// @param stindex Index of statemnent being executed
/// @param bind MYSQL_BIND array that has been created for the type
...
...
@@ -422,18 +435,28 @@ private:
/// failed.
bool
addLease
(
StatementIndex
stindex
,
std
::
vector
<
MYSQL_BIND
>&
bind
);
/// @brief
Get Lease Common Code
/// @brief
Binds Parameters and Executes
///
/// This method performs the common actions for obtaining a single lease
/// from the database.
/// This method abstracts a lot of common processing from the getXxxx()
/// methods. It binds the parameters passed to it to the appropriate
/// prepared statement, and binds the variables in the exchange6 object to
/// the output parameters of the statement. It then executes the prepared
/// statement.
///
/// @param stindex Index of statement being executed
/// @param inbind MYSQL_BIND array for input parameters
/// The data can be retrieved using mysql_stmt_fetch and the getLeaseData()
/// method on the appropriate exchange object.
///
/// @param stindex Index of prepared statement to be executed
/// @param exchange Exchange object to use
/// @param lease Lease object returned
template
<
typename
Exchange
,
typename
LeasePtr
>
void
getLease
(
StatementIndex
stindex
,
MYSQL_BIND
*
inbind
,
Exchange
&
exchange
,
LeasePtr
&
result
)
const
;
/// @param inbind Array of MYSQL_BIND objects representing the parameters.
/// (Note that the number is determined by the number of parameters
/// in the statement.)
///
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
template
<
typename
Exchange
>
void
bindAndExecute
(
StatementIndex
stindex
,
Exchange
&
exchange
,
MYSQL_BIND
*
inbind
)
const
;
/// @brief Get Lease Collection Common Code
///
...
...
@@ -445,32 +468,121 @@ private:
/// @param exchange Exchange object to use
/// @param lease LeaseCollection object returned. Note that any data in
/// the collection is cleared before new data is added.
/// @param single If true, only a single data item is to be retrieved.
/// If more than one is present, a MultipleRecords exception will
/// be thrown.
///
/// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
/// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
/// from the database where only one was expected.
template
<
typename
Exchange
,
typename
LeaseCollection
>
void
getLeaseCollection
(
StatementIndex
stindex
,
MYSQL_BIND
*
inbind
,
Exchange
&
exchange
,
LeaseCollection
&
result
)
const
;
Exchange
&
exchange
,
LeaseCollection
&
result
,
bool
single
=
false
)
const
;
/// @brief
Binds Parameters and Executes
/// @brief
Get Lease Collection
///
/// This method abstracts a lot of common processing from the getXxxx()
/// methods. It binds the parameters passed to it to the appropriate
/// prepared statement, and binds the variables in the exchange6 object to
/// the output parameters of the statement. It then executes the prepared
/// statement.
/// Gets a collection of Lease4 objects. This is just an interface to
/// the get lease collection common code.
///
/// The data can be retrieved using mysql_stmt_fetch and the getLeaseData()
/// method on the appropriate exchange object.
/// @param stindex Index of statement being executed
/// @param inbind MYSQL_BIND array for input parameters
/// @param lease LeaseCollection object returned. Note that any data in
/// the collection is cleared before new data is added.
///
/// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
/// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
/// from the database where only one was expected.
void
getLeaseCollection
(
StatementIndex
stindex
,
MYSQL_BIND
*
inbind
,
Lease4Collection
&
result
)
const
{
getLeaseCollection
(
stindex
,
inbind
,
exchange4_
,
result
);
}
/// @brief Get Lease Collection
///
/// Gets a collection of Lease6 objects. This is just an interface to
/// the get lease collection common code.
///
/// @param stindex Index of statement being executed
/// @param inbind MYSQL_BIND array for input parameters
/// @param lease LeaseCollection object returned. Note that any data in
/// the collection is cleared before new data is added.
///
/// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
/// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
/// from the database where only one was expected.
void
getLeaseCollection
(
StatementIndex
stindex
,
MYSQL_BIND
*
inbind
,
Lease6Collection
&
result
)
const
{
getLeaseCollection
(
stindex
,
inbind
,
exchange6_
,
result
);
}
/// @brief Get Lease6 Common Code
///
/// This method performs the common actions for the various getLease4()
/// methods. It acts as an interface to the getLeaseCollection() method,
/// but retrieveing only a single lease.
///
/// @param stindex Index of statement being executed
/// @param inbind MYSQL_BIND array for input parameters
/// @param lease Lease4 object returned
void
getLease
(
StatementIndex
stindex
,
MYSQL_BIND
*
inbind
,
Lease4Ptr
&
result
)
const
;
/// @brief Get Lease4 Common Code
///
/// This method performs the common actions for the various getLease4()
/// methods. It acts as an interface to the getLeaseCollection() method,
/// but retrieveing only a single lease.
///
/// @param stindex Index of statement being executed
/// @param inbind MYSQL_BIND array for input parameters
/// @param lease Lease6 object returned
void
getLease
(
StatementIndex
stindex
,
MYSQL_BIND
*
inbind
,
Lease6Ptr
&
result
)
const
;
/// @brief Update lease common code
///
/// Holds the common code for updating a lease. It binds the parameters
/// to the prepared statement, executes it, then checks how many rows
/// were affected.
///
/// @param stindex Index of prepared statement to be executed
/// @param exchange Exchange object to use
/// @param inbind Array of MYSQL_BIND objects representing the parameters.
/// @param bind Array of MYSQL_BIND objects representing the parameters.
/// (Note that the number is determined by the number of parameters
/// in the statement.)
/// @param lease Pointer to the lease object whose record is being updated.
///
/// @throw NoSuchLease Could not update a lease because no lease matches
/// the address given.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
template
<
typename
Exchange
>
void
bindAndExecute
(
StatementIndex
stindex
,
Exchange
&
exchange
,
MYSQL_BIND
*
inbind
)
const
;
template
<
typename
LeasePtr
>
void
updateLease
(
StatementIndex
stindex
,
MYSQL_BIND
*
bind
,
const
LeasePtr
&
lease
);
/// @brief Delete lease common code
///
/// Holds the common code for deleting a lease. It binds the parameters
/// to the prepared statement, executes the statement and checks to
/// see how many rows were deleted.
///
/// @param stindex Index of prepared statement to be executed
/// @param bind Array of MYSQL_BIND objects representing the parameters.
/// (Note that the number is determined by the number of parameters
/// in the statement.)
///
/// @return true if one or more rows were deleted, false if none were
/// deleted.
///
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
bool
deleteLease
(
StatementIndex
stindex
,
MYSQL_BIND
*
bind
);
/// @brief Check Error and Throw Exception
///
...
...
@@ -496,15 +608,15 @@ private:
// Members
/// Used for transfer of data to/from the database. This is a pointed-to
/// object as its contents may change in "const" calls, while the rest
/// of this object does not. (At alternative would be to declare it as
/// "mutable".)
MYSQL
*
mysql_
;
///< MySQL context object
std
::
vector
<
std
::
string
>
text_statements_
;
///< Raw text of statements
std
::
vector
<
MYSQL_STMT
*>
statements_
;
///< Prepared statements
/// The exchange objects are used for transfer of data to/from the database.
/// They are pointed-to objects as the contents may change in "const" calls,
/// while the rest of this object does not. (At alternative would be to
/// declare them as "mutable".)
boost
::
scoped_ptr
<
MySqlLease4Exchange
>
exchange4_
;
///< Exchange object
boost
::
scoped_ptr
<
MySqlLease6Exchange
>
exchange6_
;
///< Exchange object
MYSQL
*
mysql_
;
///< MySQL context object
std
::
vector
<
MYSQL_STMT
*>
statements_
;
///< Prepared statements
std
::
vector
<
std
::
string
>
text_statements_
;
///< Raw text of statements
};
};
// end of isc::dhcp namespace
...
...
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
View file @
5b1850bb
This diff is collapsed.
Click to expand it.
src/lib/dhcpsrv/tests/schema_copy.h
View file @
5b1850bb
...
...
@@ -75,7 +75,7 @@ const char* create_statement[] = {
"minor INT"
")"
,
"INSERT INTO schema_version VALUES (0,
1
)"
,
"INSERT INTO schema_version VALUES (0,
2
)"
,
NULL
};
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment