debugging a database query in the context of Drupal development - php

I am developing a dynamic web page using Drupal 7. I ran into a very strange problem. I have reduced my problem to a very small test case as follows:
The database side: I am using MySQL. I have two tables as defined here with a few sample data entries:
CREATE TABLE people (
pid INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
INSERT INTO people VALUES (NULL, 'Joe');
INSERT INTO people VALUES (NULL, 'Ant');
INSERT INTO people VALUES (NULL, 'Tom');
CREATE TABLE event (
eid INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
pid INT(6) UNSIGNED NOT NULL,
event_desc VARCHAR(20) NOT NULL,
event_date DATE NOT NULL
);
INSERT INTO event VALUES (NULL, 1, '1p', '2015-10-01');
INSERT INTO event VALUES (NULL, 2, '1p', '2015-10-12');
INSERT INTO event VALUES (NULL, 2, '2p', '2015-10-00');
INSERT INTO event VALUES (NULL, 2, '3p', '2015-00-00');
INSERT INTO event VALUES (NULL, 3, '1p', '2010-07-18');
INSERT INTO event VALUES (NULL, 3, '2p', '2010-09-00');
Note the only interesting feature here is that the event table may contain incomplete event date in the format of '2015-10-00' when date is unknown and '2015-00-00' when both date and month are unknown. I understand this is legal in MySQL.
The Drupal 7 side: I create a custom module as follows:
<?php
/**
* Implements hook_menu().
*/
function test_menu() {
$items['test'] = array(
'type' => MENU_NORMAL_ITEM,
'title' => 'Test',
'description' => 'Test',
'page callback' => '_test_page',
'access callback' => TRUE,
'menu_name' => 'main-menu',
);
return $items;
}
function _test_page() {
$output = "";
$sql = "SELECT event.event_desc, event.event_date, people.name FROM event, ";
$sql .= "people WHERE event.pid=people.pid ORDER BY people.name";
$result = db_query($sql);
foreach ($result as $data) {
$output .= $data->name. " ". $data->event_desc. " ". $data->event_date. "<br>\n";
}
return $output;
}
Note that I create a page with a menu item 'Test'. The page performs a query and displays the result. Now the very strange part: when the event date is incomplete, the query gets the wrong data '0000-00-00' as shown in the screen capture below:
Some analysis:
This does not seem to be a problem with my SQL, because when I try the
identical query from mysql command line I can get the correct result.
This only happens when I join the two tables. If I simply query the 'event' table, I can get the correct result display in that Drupal page.
When passing the SQL from a standalone PHP script to the database via PDO, there's no problem.
I learned from Drupal's forum that the Date type is not used in Drupal core. So it may or may not be well supported.
Because the debugging isn't straight-forward to do, I decided not to pursue it any longer. The simplest way around the bug was to use VARCHAR(10) to store the Date information. By making that simple change, my web page can function properly. Of course I would lose the ability to use any Date-related functions.

'2015-00-00' is legal in mysql. I wonder if is legal in PHP. What is the data type of $data->event_date ? Just make sure to print it as string and not as a formatted date.

Related

Update dynamic SQL table with history table

I was successful in implementing a table's history (for auditing) based on this answer that basically suggests to create two tables wherein one contains the current version and the other a table of versions with the real data:
CREATE TABLE dbo.Page(
ID int PRIMARY KEY,
Name nvarchar(200) NOT NULL,
CreatedByName nvarchar(100) NOT NULL,
CurrentRevision int NOT NULL,
CreatedDateTime datetime NOT NULL
CREATE TABLE dbo.PageContent(
PageID int NOT NULL,
Revision int NOT NULL,
Title nvarchar(200) NOT NULL,
User nvarchar(100) NOT NULL,
LastModified datetime NOT NULL,
Comment nvarchar(300) NULL,
Content nvarchar(max) NOT NULL,
Description nvarchar(200) NULL
My UPDATEs to static, non-dynamic fields are OK where the versioning is recognised correctly (rough pseudocode adapted from dev't code):
$sql_pagecontent = array(
'PageID' => // logic to get PageID
'Title' => $data['Title']
'User' => $data['User'],
'LastModified' => $this->get_date(),
'Comment' => $data['Comment'],
'Content' => $data['Content'],
'Description' => $data['Description']
);
$this->db->insert('PageContent', $sql_pagecontent);
$id_version = $this->db->insert_id();
$sql_page = array(
'CurrentRevision' => $id_version
);
if($type_version === 'UPDATE')
{
$this->db->where('ID', $page_id_to_replace);
$this->db->update('Page', $sql_page);
}
else
{
$this->db->insert('Page', $sql_page);
}
The problem is when I apply this to dynamic tables with UPDATE.
During UPDATE, I want to achieve the following, just similar to the non-dynamic tables (context of Page and PageContent):
a new PageContent row is INSERTed (new PageContent), a new row of CurrentRevision in Page is added
a new PageContent row is INSERTed (update of existing PageContent), the CurrentRevision in Page UPDATEd with this new row
deleted PageContents are removed from Page
NOTE I want each row of these dynamic tables preserved in both Page and PageContent if ever the user wants to check the changes so I just cannot do INSERT which will just flood the table.
I cannot seem to filter these dynamic values correctly with the current implementation.
Please advise on how to deal with this use case.
Solution
I am not sure if my current implementation of my said requirements is correct, feasible or recommended but it does the job:
get ID of current PageContent being modified
get ID of current Page being modified
delete all rows of Page that has the ID of #1
perform INSERT and apply UPDATE as type of action of PageContent
Number 3 is key to make sure whether existing rows are edited/deleted or new rows added are saved as the current version in Page. Number 4 puts this into place to make sure every row is recognised.

Ordering Mechanism in Mysql

I have a matter in PHP & Mysql Project.
Simply, I have two tables project and project features,
Every project has as specific features.
CREATE TABLE projects (
ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name varchar(255) NOT NULL
);
CREATE TABLE projects_features (
projectId INT NOT NULL,
name varchar(255) NOT NULL,
value varchar(255) NOT NULL,
weight INT NOT NULL
);
INSERT INTO projects VALUES (NULL,'project1');
INSERT INTO projects VALUES (NULL,'project2');
INSERT INTO projects_features VALUES (1,'Feature1','Feature1 Value',1);
INSERT INTO projects_features VALUES (2,'Feature2','Feature2 Value',2);
INSERT INTO projects_features VALUES (1,'Feature3','Feature3 Value',3);
INSERT INTO projects_features VALUES (2,'Feature4','Feature4 Value',4);
INSERT INTO projects_features VALUES (1,'Feature5','Feature5 Value',5);
I Get the Project features by:
SELECT * FROM projects_features WHERE projectId = 1 ORDER BY weight ASC;
So the bigger weight will be down and lower weight will be Up.
Now,
In My View I have move up and move down buttons, so I can re-sort project features.
I can firstly select the current item weight then select the upper item weight ,
then type two update queries to exchange the weight between the two rows,
but it's not a professional way , I don't like to use four queries.
I need to do it in one query Instead of four queries.
Can anybody help please ?
Here's how I'd tackle this, assuming I've understood the question.
First, I'd add a featureId column to projects_features, and make (projectId, featureId) the composite primary key. This isn't actually necessary to my solution; it just makes the rest a whole lot easier. For the next part you need to be able to reference individual records in projects_features.
Next, I'd have the Move buttons populate an array in PHP, like
$update_list = array();
$update_list[i] = array(':project_id' => $proj_id,
':feature_id' => $feat_id,
':new_weight' => $weight);
Finally, I'd do the update through a PHP function that encapsulates the UPDATE statements, like this:
function updateWeights($update_array, $dbh)
{
$sql = 'UPDATE project_features
SET weight = :new_weight
WHERE projectId = :project_id
AND featureId = :feature_id'
$stmt = $dbh->prepare($sql);
foreach ($update_array as $update_item)
{
$stmt->execute($update_item);
}
}
Note I'm using PDO here; it could also be done with mysqli, although mysqli doesn't support named bind parameters so the syntax would be slightly different, something like
function updateWeights($update_array, $dbh)
{
$sql = 'UPDATE project_features
SET weight = ?
WHERE projectId = ?
AND featureId = ?'
$stmt = $dbh->prepare($sql);
foreach ($update_array as $update_item)
{
$stmt->bind_param('i', $update_item[':new_weight']);
$stmt->bind_param('i', $update_item[':project_id']);
$stmt->bind_param('i', $update_item[':feature_id']);
$stmt->execute();
}
}
Also note that I haven't actually run this code, and so make no representation that it is free of syntax or other errors.
I hope that helps.

MSSQL "String or binary data would be truncated." Error on Update

I have a MSSQL table users:
CREATE TABLE users (
ID int IDENTITY(1,1) NOT NULL,
firstname nvarchar(20) NOT NULL,
lastname nvarchar(20) NOT NULL,
dir bit NOT NULL,
cc nvarchar(15),
readyacc bit NOT NULL,
region nvarchar(50),
org nvarchar(50),
suborg nvarchar(50),
section nvarchar(50),
title nvarchar(50),
floor tinyint,
wkstn nvarchar(50),
fc nvarchar(15)
);
And I'm trying to update an existing entry with the prepared query:
UPDATE users SET ? = ? WHERE ID=?;
With my parameters as:
Array ( [0] => title [1] => Teleco [2] => 1 )
But it seems as though if the string length is greater than 5 it gives me the error "String or binary data would be truncated.". Eg, Telec works but Teleco does not. When I try the same query in the SQL Management Studio it gives me no errors.
Am I just missing something obvious? Please help
Seeing someone else decided to post an answer, transcribing my comment to an answer.
That's just it, you can't bind tables/columns with UPDATE users SET ? = ? WHERE ID=?;.
Use a safelist if anything.
You can do this though $var="x"; by assigning a pre-defined variable "ahead of time".
Then doing UPDATE users SET '$var' = ? WHERE ID=?;
You see, tables/columns require a hard coded value or a "lookahead" in order to know what it's supposed to use as far as table/column names go, before binding begins. This all happens "after" the query, therefore a "lookahead" can be in the form of a variable.
FYI: The same applies for MySQL and is not specific to MSSQL.
Your query, when the parameters are passed in, it equivalent to this:
UPDATE users SET 'title' = 'Teleco' WHERE ID='1';
Which would not work if you tried running it in the management studio. The error message you're getting is erroneous. As the comment by Fred says, you need to have a safe(white) list of columns that can be updated:
$safe_cols = ['dir', 'cc', 'title'];
if(in_array($col, $safe_cols, true))
{
$stmt = $db->prepare('UPDATE users SET ' . $col . ' = ? WHERE id = ?');
// bind params and execute
}

php = insert working but want to check if in db already based on one field

Have a working system where I am posting news articles. I sometimes add the same one twice and want to avoid this. So how can I alter my insert statement to first check for a match on the field named 'title' to see if it is equal to the title I have in the record I am trying to submit?
Here is php code I use, as it was done for me since I am a PHP novice but I do not know how to check for the title=title and then not add it if it finds it or to add it if it doesnt find a match:
$result = mysql_query("
insert into news (
catalogid,
title,
intro,
content,
viewnum,
adddate,
rating,
ratenum,
source,
sourceurl,
isdisplay,
isfeature,
subip,
vsent,
timesubmitted)
values
('1',
'$title',
'$intro',
'$content',
'0',
'$subdate',
'$source',
'$icheck',
'N/A',
'$sourceurl',
'$isapp',
'0',
'127.0.0.1',
'0',
'$tsdate')"
);
Thank you!
There are database-specific tricks like on duplicate key update, but typically you simply test for the existence of a record with the same key via a select. If the record exists, you update it with the new data, otherwise you insert a new record.
You can run a check query before inserting , like:
$sql = mysql_query("SELECT catalogid from news WHERE title='".$yourTitle."'");
if(mysql_num_rows($sql) < 1) {
//add your insert query here
}
Hope that helps
$result = mysql_query("SELECT * FROM news WHERE `title`=$title");
if (!$result)
{
// your code INSERT
$result = mysql_query("
insert into news (
catalogid,
title,
intro,
content,
viewnum,
adddate,
rating,
ratenum,
source,
sourceurl,
isdisplay,
isfeature,
subip,
vsent,
timesubmitted)
values
('1',
'$title',
'$intro',
'$content',
'0',
'$subdate',
'$source',
'$icheck',
'N/A',
'$sourceurl',
'$isapp',
'0',
'127.0.0.1',
'0',
'$tsdate')"
);
}
You can't do this with a single INSERT statement, at least not directly. If you set the title field in your database table to UNIQUE, you can prevent MySQL from inserting a record with a duplicate title. You will need to detect if the mysql_query function returns FALSE; if it does, you know a duplicate record was inserted and you can handle this however you see fit.

MySQL/PHP - inserting an extra row on INSERT statement

I am working on a small PHP + MySQL application. I have run into an issue where one of my insert statements seems to cause MySQL to add an additional somewhat blank row after each insert. Here's my setup:
I have a function that does the insert:
function addFacCertification($facultyID,$month,$year,$completed,$certificatesent,$confirmationtodean,$comments)
{
//echo "$facultyID<br>$month<br>$year<br>$completed<br>$certificatesent<br>$confirmationtodean<br>$comments";
$today = getdate();
$month = $today["mon"];
$date = $today["mday"];
$year = $today["year"];
$curdate = "$year" ."-". "$month" . "-" . "$date";
$sql = mysql_query("INSERT INTO facultycertification (facultyID,cmonth, cyear, completed, certificatesent, confirmationtodean, comments, certifiedon)
VALUES ('$facultyID', '$month', '$year', '$completed', '$certificatesent', '$confirmationtodean','$comments', '$curdate')");
return $sql;
}
Function call:
$sqlresults = addFacCertification($_POST["facultyID"], $_POST["month"], $_POST["year"], $_POST["completed"], $_POST["csent"],
$_POST["ctodean"], $_POST["comments"]);
Problem is, every time this is run - I get an extra row in my table: (See second row below) Image is here:
Any ideas why? Here is the table structure:
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
facultyID INT NOT NULL,
cmonth INT NOT NULL,
cyear INT NOT NULL,
completed INT NOT NULL,
certificatesent INT NOT NULL,
confirmationtodean INT NOT NULL,
comments TEXT,
certifiedon DATE,
FOREIGN KEY (facultyID) REFERENCES faculty(id)
I would imagine that your code is somehow being executed twice. I'd suggest the following (in this order) to debug the issue:
Uncomment that echo inside the function and see if it echos out twice (i.e. you're calling the function twice)
If you're running this via a web request tail the web server log to see if the request is being made twice
Enable general logging or enable profiling on the MySQL server to see what SQL queries are actually run against the server
You could have something like a MySQL trigger on the db doing this if you didn't set it up yourself but I think this is unlikely.

Categories